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 ];
-}