diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index b944ac97275fa..2d92fe962aade 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-editor/src/components/use-no-recursive-renders/index.js b/packages/block-editor/src/components/use-no-recursive-renders/index.js new file mode 100644 index 0000000000000..94ae4b7c19263 --- /dev/null +++ b/packages/block-editor/src/components/use-no-recursive-renders/index.js @@ -0,0 +1,49 @@ +/** + * WordPress dependencies + */ +import { + createContext, + useCallback, + useContext, + useMemo, +} from '@wordpress/element'; + +const RenderedRefsContext = createContext( new Set() ); + +// Immutably add to a Set +function add( set, element ) { + const result = new Set( set ); + result.add( 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 ); + const newRenderedBlocks = useMemo( + () => add( previouslyRenderedBlocks, uniqueId ), + [ uniqueId, previouslyRenderedBlocks ] + ); + const Provider = useCallback( + ( { children } ) => ( + + { children } + + ), + [ newRenderedBlocks ] + ); + return [ hasAlreadyRendered, Provider ]; +} 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 a9d9f6e53b789..0844dd601b3a2 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 09b4f058a5227..36dfe86da95f1 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 diff --git a/packages/block-library/src/block/use-no-recursive-renders.js b/packages/block-library/src/block/use-no-recursive-renders.js deleted file mode 100644 index b17ddd9b4f618..0000000000000 --- a/packages/block-library/src/block/use-no-recursive-renders.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * WordPress dependencies - */ -import { - createContext, - useCallback, - useContext, - useMemo, -} from '@wordpress/element'; - -const RenderedRefsContext = createContext( [] ); - -export default function useNoRecursiveRenders( ref ) { - const previouslyRenderedRefs = useContext( RenderedRefsContext ); - const hasAlreadyRendered = previouslyRenderedRefs.includes( ref ); - const newRenderedRefs = useMemo( () => [ ...previouslyRenderedRefs, ref ], [ - ref, - previouslyRenderedRefs, - ] ); - const Provider = useCallback( - ( { children } ) => ( - - { children } - - ), - [ newRenderedRefs ] - ); - return [ hasAlreadyRendered, Provider ]; -}