From 68b69d3a0c471c6a6cb9c552b79b5ec8692b1814 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Fri, 22 Jan 2021 18:04:06 +0000 Subject: [PATCH 1/4] Block Editor: Add useNoRecursiveRenders Originally introduced in #28405 to prevent Reusable Blocks from infinitely and fatally recurring, React hook `useNoRecursiveRenders` has a place in the block-editor package so that other block types susceptible to recursion can be fixed too. --- packages/block-editor/src/components/index.js | 1 + .../src/components/use-no-recursive-renders/index.js} | 0 .../test/use-no-recursive-renders.js | 6 +----- packages/block-library/src/block/edit.js | 6 +----- 4 files changed, 3 insertions(+), 10 deletions(-) rename packages/{block-library/src/block/use-no-recursive-renders.js => block-editor/src/components/use-no-recursive-renders/index.js} (100%) rename packages/{block-library/src/block => block-editor/src/components/use-no-recursive-renders}/test/use-no-recursive-renders.js (96%) diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index b944ac97275fac..2d92fe962aadef 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -121,6 +121,7 @@ export { default as WritingFlow } from './writing-flow'; export { useCanvasClickRedirect as __unstableUseCanvasClickRedirect } from './use-canvas-click-redirect'; export { default as useBlockDisplayInformation } from './use-block-display-information'; export { default as __unstableIframe } from './iframe'; +export { default as __experimentalUseNoRecursiveRenders } from './use-no-recursive-renders'; /* * State Related Components diff --git a/packages/block-library/src/block/use-no-recursive-renders.js b/packages/block-editor/src/components/use-no-recursive-renders/index.js similarity index 100% rename from packages/block-library/src/block/use-no-recursive-renders.js rename to packages/block-editor/src/components/use-no-recursive-renders/index.js diff --git a/packages/block-library/src/block/test/use-no-recursive-renders.js b/packages/block-editor/src/components/use-no-recursive-renders/test/use-no-recursive-renders.js similarity index 96% rename from packages/block-library/src/block/test/use-no-recursive-renders.js rename to packages/block-editor/src/components/use-no-recursive-renders/test/use-no-recursive-renders.js index a9d9f6e53b789a..0844dd601b3a25 100644 --- a/packages/block-library/src/block/test/use-no-recursive-renders.js +++ b/packages/block-editor/src/components/use-no-recursive-renders/test/use-no-recursive-renders.js @@ -7,6 +7,7 @@ import { render } from '@testing-library/react'; * WordPress dependencies */ import { Fragment } from '@wordpress/element'; +import { __experimentalUseNoRecursiveRenders as useNoRecursiveRenders } from '@wordpress/block-editor'; // Mimics a block's Edit component, such as ReusableBlockEdit, which is capable // of calling itself depending on its `ref` attribute. @@ -37,11 +38,6 @@ function Edit( { attributes: { ref } } ) { ); } -/** - * Internal dependencies - */ -import useNoRecursiveRenders from '../use-no-recursive-renders'; - describe( 'useNoRecursiveRenders', () => { it( 'allows a single block to render', () => { const { container } = render( diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 09b4f058a5227a..36dfe86da95f15 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -18,6 +18,7 @@ import { import { __ } from '@wordpress/i18n'; import { __experimentalUseInnerBlocksProps as useInnerBlocksProps, + __experimentalUseNoRecursiveRenders as useNoRecursiveRenders, InnerBlocks, BlockControls, InspectorControls, @@ -26,11 +27,6 @@ import { } from '@wordpress/block-editor'; import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; -/** - * Internal dependencies - */ -import useNoRecursiveRenders from './use-no-recursive-renders'; - export default function ReusableBlockEdit( { attributes: { ref }, clientId } ) { const [ hasAlreadyRendered, RecursionProvider ] = useNoRecursiveRenders( ref From 39509b213df1362eb09d8d329de1cd3adf35fff6 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Fri, 22 Jan 2021 18:10:43 +0000 Subject: [PATCH 2/4] Update variables to reflect uses beyond Reusable Block --- .../use-no-recursive-renders/index.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/use-no-recursive-renders/index.js b/packages/block-editor/src/components/use-no-recursive-renders/index.js index b17ddd9b4f6186..30b9b2a99456d3 100644 --- a/packages/block-editor/src/components/use-no-recursive-renders/index.js +++ b/packages/block-editor/src/components/use-no-recursive-renders/index.js @@ -10,20 +10,20 @@ import { const RenderedRefsContext = createContext( [] ); -export default function useNoRecursiveRenders( ref ) { - const previouslyRenderedRefs = useContext( RenderedRefsContext ); - const hasAlreadyRendered = previouslyRenderedRefs.includes( ref ); - const newRenderedRefs = useMemo( () => [ ...previouslyRenderedRefs, ref ], [ - ref, - previouslyRenderedRefs, - ] ); +export default function useNoRecursiveRenders( uniqueId ) { + const previouslyRenderedBlocks = useContext( RenderedRefsContext ); + const hasAlreadyRendered = previouslyRenderedBlocks.includes( uniqueId ); + const newRenderedBlocks = useMemo( + () => [ ...previouslyRenderedBlocks, uniqueId ], + [ uniqueId, previouslyRenderedBlocks ] + ); const Provider = useCallback( ( { children } ) => ( - + { children } ), - [ newRenderedRefs ] + [ newRenderedBlocks ] ); return [ hasAlreadyRendered, Provider ]; } From 16d76da793ed2bb3722e8ffef3487c9f0d764c30 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Fri, 22 Jan 2021 18:18:48 +0000 Subject: [PATCH 3/4] useNoRecursiveRenders: Use Set instead of Array --- .../components/use-no-recursive-renders/index.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/use-no-recursive-renders/index.js b/packages/block-editor/src/components/use-no-recursive-renders/index.js index 30b9b2a99456d3..942177db556851 100644 --- a/packages/block-editor/src/components/use-no-recursive-renders/index.js +++ b/packages/block-editor/src/components/use-no-recursive-renders/index.js @@ -8,13 +8,20 @@ import { useMemo, } from '@wordpress/element'; -const RenderedRefsContext = createContext( [] ); +const RenderedRefsContext = createContext( new Set() ); + +// Immutably add to a Set +function add( set, element ) { + const result = new Set( set ); + result.add( element ); + return result; +} export default function useNoRecursiveRenders( uniqueId ) { const previouslyRenderedBlocks = useContext( RenderedRefsContext ); - const hasAlreadyRendered = previouslyRenderedBlocks.includes( uniqueId ); + const hasAlreadyRendered = previouslyRenderedBlocks.has( uniqueId ); const newRenderedBlocks = useMemo( - () => [ ...previouslyRenderedBlocks, uniqueId ], + () => add( previouslyRenderedBlocks, uniqueId ), [ uniqueId, previouslyRenderedBlocks ] ); const Provider = useCallback( From 1c3b036e2e6ecc10456d99d8d394c9264ba815c6 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Fri, 22 Jan 2021 19:21:33 +0000 Subject: [PATCH 4/4] Add JSDoc comment. --- .../components/use-no-recursive-renders/index.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/block-editor/src/components/use-no-recursive-renders/index.js b/packages/block-editor/src/components/use-no-recursive-renders/index.js index 942177db556851..94ae4b7c192632 100644 --- a/packages/block-editor/src/components/use-no-recursive-renders/index.js +++ b/packages/block-editor/src/components/use-no-recursive-renders/index.js @@ -17,6 +17,19 @@ function add( set, element ) { return result; } +/** + * A React hook for keeping track of blocks previously rendered up in the block + * tree. Blocks susceptible to recursiion can use this hook in their `Edit` + * function to prevent said recursion. + * + * @param {*} uniqueId Any value that acts as a unique identifier for a block instance. + * + * @return {[boolean, Function]} A tuple of: + * - a boolean describing whether the provided id + * has already been rendered; + * - a React context provider to be used to wrap + * other elements. + */ export default function useNoRecursiveRenders( uniqueId ) { const previouslyRenderedBlocks = useContext( RenderedRefsContext ); const hasAlreadyRendered = previouslyRenderedBlocks.has( uniqueId );