From e34777f0fac3a1d49fa3ab9ab8791e6eca788f50 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Mon, 20 Mar 2023 12:33:39 +0400 Subject: [PATCH] Plugins: Refactor the 'PluginArea' component to use the sync store --- package-lock.json | 1 + packages/plugins/README.md | 6 + packages/plugins/package.json | 1 + .../src/components/plugin-area/index.js | 125 ++++++++---------- .../src/components/test/plugin-area.js | 38 +++--- 5 files changed, 81 insertions(+), 90 deletions(-) diff --git a/package-lock.json b/package-lock.json index 828769e19e435e..f73c82be4d8979 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17539,6 +17539,7 @@ "@wordpress/element": "file:packages/element", "@wordpress/hooks": "file:packages/hooks", "@wordpress/icons": "file:packages/icons", + "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "memize": "^1.1.0" } }, diff --git a/packages/plugins/README.md b/packages/plugins/README.md index 8b7fbaf5d922d3..a5fee590d4fd18 100644 --- a/packages/plugins/README.md +++ b/packages/plugins/README.md @@ -68,6 +68,12 @@ const Layout = () => ( ); ``` +_Parameters_ + +- _props_ `Object`: +- _props.scope_ `string|undefined`: +- _props.onError_ `Function|undefined`: + _Returns_ - `WPComponent`: The component to be rendered. diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 881e6fdf4530a0..8eb18d8512da37 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -30,6 +30,7 @@ "@wordpress/element": "file:../element", "@wordpress/hooks": "file:../hooks", "@wordpress/icons": "file:../icons", + "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "memize": "^1.1.0" }, "peerDependencies": { diff --git a/packages/plugins/src/components/plugin-area/index.js b/packages/plugins/src/components/plugin-area/index.js index 30c9d59371206e..72147dba16292d 100644 --- a/packages/plugins/src/components/plugin-area/index.js +++ b/packages/plugins/src/components/plugin-area/index.js @@ -6,8 +6,9 @@ import memoize from 'memize'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { useMemo, useSyncExternalStore } from '@wordpress/element'; import { addAction, removeAction } from '@wordpress/hooks'; +import isShallowEqual from '@wordpress/is-shallow-equal'; /** * Internal dependencies @@ -16,9 +17,14 @@ import { PluginContextProvider } from '../plugin-context'; import { PluginErrorBoundary } from '../plugin-error-boundary'; import { getPlugins } from '../../api'; +const getPluginContext = memoize( ( icon, name ) => ( { icon, name } ) ); + /** * A component that renders all plugin fills in a hidden div. * + * @param {Object} props + * @param {string|undefined} props.scope + * @param {Function|undefined} props.onError * @example * ```js * // Using ES5 syntax @@ -50,80 +56,61 @@ import { getPlugins } from '../../api'; * * @return {WPComponent} The component to be rendered. */ -class PluginArea extends Component { - constructor() { - super( ...arguments ); - - this.setPlugins = this.setPlugins.bind( this ); - this.memoizedContext = memoize( ( name, icon ) => { - return { - name, - icon, - }; - } ); - this.state = this.getCurrentPluginsState(); - } +function PluginArea( { scope, onError } ) { + const store = useMemo( () => { + let lastValue; - getCurrentPluginsState() { return { - plugins: getPlugins( this.props.scope ).map( - ( { icon, name, render } ) => { - return { - Plugin: render, - context: this.memoizedContext( name, icon ), - }; - } - ), - }; - } + subscribe( listener ) { + addAction( + 'plugins.pluginRegistered', + 'core/plugins/plugin-area/plugins-registered', + listener + ); + addAction( + 'plugins.pluginUnregistered', + 'core/plugins/plugin-area/plugins-unregistered', + listener + ); + return () => { + removeAction( + 'plugins.pluginRegistered', + 'core/plugins/plugin-area/plugins-registered' + ); + removeAction( + 'plugins.pluginUnregistered', + 'core/plugins/plugin-area/plugins-unregistered' + ); + }; + }, + getValue() { + const nextValue = getPlugins( scope ); - componentDidMount() { - addAction( - 'plugins.pluginRegistered', - 'core/plugins/plugin-area/plugins-registered', - this.setPlugins - ); - addAction( - 'plugins.pluginUnregistered', - 'core/plugins/plugin-area/plugins-unregistered', - this.setPlugins - ); - } + if ( ! isShallowEqual( lastValue, nextValue ) ) { + lastValue = nextValue; + } - componentWillUnmount() { - removeAction( - 'plugins.pluginRegistered', - 'core/plugins/plugin-area/plugins-registered' - ); - removeAction( - 'plugins.pluginUnregistered', - 'core/plugins/plugin-area/plugins-unregistered' - ); - } + return lastValue; + }, + }; + }, [ scope ] ); - setPlugins() { - this.setState( this.getCurrentPluginsState ); - } + const plugins = useSyncExternalStore( store.subscribe, store.getValue ); - render() { - return ( -
- { this.state.plugins.map( ( { context, Plugin } ) => ( - - - - - - ) ) } -
- ); - } + return ( +
+ { plugins.map( ( { icon, name, render: Plugin } ) => ( + + + + + + ) ) } +
+ ); } export default PluginArea; diff --git a/packages/plugins/src/components/test/plugin-area.js b/packages/plugins/src/components/test/plugin-area.js index 68ed2b5368d032..42ea556081ae2a 100644 --- a/packages/plugins/src/components/test/plugin-area.js +++ b/packages/plugins/src/components/test/plugin-area.js @@ -92,30 +92,26 @@ describe( 'PluginArea', () => { expect( container ).toHaveTextContent( 'plugin: two.' ); } ); - test.failing( - 'does not rerender when a plugin is added to a different scope', - () => { - const ComponentSpy = jest.fn( ( { content } ) => { - return `plugin: ${ content }.`; - } ); + test( 'does not rerender when a plugin is added to a different scope', () => { + const ComponentSpy = jest.fn( ( { content } ) => { + return `plugin: ${ content }.`; + } ); - registerPlugin( 'scoped', { - render: () => , - icon: 'smiley', - scope: 'my-app', - } ); + registerPlugin( 'scoped', { + render: () => , + icon: 'smiley', + scope: 'my-app', + } ); - render( ); + render( ); - act( () => { - registerPlugin( 'unscoped', { - render: () => , - icon: 'smiley', - } ); + act( () => { + registerPlugin( 'unscoped', { + render: () => , + icon: 'smiley', } ); + } ); - // Any store update triggers setState and causes PluginArea to rerender. - expect( ComponentSpy ).toHaveBeenCalledTimes( 1 ); - } - ); + expect( ComponentSpy ).toHaveBeenCalledTimes( 1 ); + } ); } );