diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js
index c0d311c254cba..0c284f50049b4 100644
--- a/packages/block-editor/src/components/block-list/index.js
+++ b/packages/block-editor/src/components/block-list/index.js
@@ -24,9 +24,8 @@ import {
useLayoutEffect,
useContext,
useRef,
- Fragment,
- createElement,
useReducer,
+ useId,
} from '@wordpress/element';
/**
@@ -53,42 +52,79 @@ const pendingBlockVisibilityUpdatesPerRegistry = new WeakMap();
function useRootPortal( component ) {
const components = useContext( componentsContext );
+ const id = useId();
+ // Run every time the component rerenders.
useLayoutEffect( () => {
- if ( ! component ) return;
- components.add( component );
- return () => {
- components.delete( component );
- };
- } );
+ if ( component ) {
+ components.render( id, component );
+ } else {
+ components.unmount( id );
+ }
+ }, [ components, id, component ] );
+
+ // Run only on unmount.
+ useLayoutEffect( () => () => components.unmount( id ), [ components, id ] );
+}
+
+function Component( { id, componentsById, renderById } ) {
+ const [ , forceRender ] = useReducer( () => ( {} ) );
+ useLayoutEffect( () => {
+ renderById.current.set( id, forceRender );
+ }, [ id, renderById ] );
+ return componentsById.current.get( id );
}
-function Components( { subscriber, components } ) {
+function Components( { componentsById, renderById, renderAll } ) {
const [ , forceRender ] = useReducer( () => ( {} ) );
- subscriber.current = forceRender;
- return createElement( Fragment, null, ...components.current );
+
+ useLayoutEffect( () => {
+ renderAll.current = forceRender;
+ }, [ renderAll ] );
+
+ return Array.from( componentsById.current.keys() ).map( ( key ) => (
+
+ ) );
}
function ComponentRenderer( { children } ) {
- const subscriber = useRef( () => {} );
- const components = useRef( new Set() );
+ const componentsById = useRef( new Map() );
+ const renderById = useRef( new Map() );
+ const renderAll = useRef( () => {} );
return (
( {
- add( component ) {
- components.current.add( component );
- subscriber.current();
+ render( id, component ) {
+ if ( componentsById.current.has( id ) ) {
+ componentsById.current.set( id, component );
+ renderById.current.get( id )();
+ } else {
+ componentsById.current.set( id, component );
+ renderAll.current();
+ }
},
- delete( component ) {
- components.current.delete( component );
- subscriber.current();
+ unmount( id ) {
+ if ( componentsById.current.has( id ) ) {
+ componentsById.current.delete( id );
+ renderById.current.delete( id );
+ renderAll.current();
+ }
},
} ),
[]
) }
>
-
+
{ children }
);
diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js
index df2d840188246..52cd2ece75300 100644
--- a/packages/block-editor/src/hooks/duotone.js
+++ b/packages/block-editor/src/hooks/duotone.js
@@ -297,9 +297,10 @@ const useDuotoneStyles = ( props, blockType, attributes ) => {
);
const id = `wp-duotone-${ useInstanceId( useDuotoneStyles ) }`;
+ const duotoneStyle = attributes?.style?.color?.duotone;
BlockList.useRootPortal(
- duotoneSupport && (
+ duotoneSupport && duotoneStyle && (
{
)
);
+ // CAUTION: code added before this line will be executed
+ // for all blocks, not just those that support duotone. Code added
+ // above this line should be carefully evaluated for its impact on
+ // performance.
return {
...props,
className: duotoneSupport