Skip to content

Commit

Permalink
Plugins: Refactor the 'PluginArea' component to use the sync store
Browse files Browse the repository at this point in the history
  • Loading branch information
Mamaduka committed Mar 21, 2023
1 parent e56e53a commit e34777f
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 90 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions packages/plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ const Layout = () => (
);
```

_Parameters_

- _props_ `Object`:
- _props.scope_ `string|undefined`:
- _props.onError_ `Function|undefined`:

_Returns_

- `WPComponent`: The component to be rendered.
Expand Down
1 change: 1 addition & 0 deletions packages/plugins/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
125 changes: 56 additions & 69 deletions packages/plugins/src/components/plugin-area/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 (
<div style={ { display: 'none' } }>
{ this.state.plugins.map( ( { context, Plugin } ) => (
<PluginContextProvider
key={ context.name }
value={ context }
>
<PluginErrorBoundary
name={ context.name }
onError={ this.props.onError }
>
<Plugin />
</PluginErrorBoundary>
</PluginContextProvider>
) ) }
</div>
);
}
return (
<div style={ { display: 'none' } }>
{ plugins.map( ( { icon, name, render: Plugin } ) => (
<PluginContextProvider
key={ name }
value={ getPluginContext( icon, name ) }
>
<PluginErrorBoundary name={ name } onError={ onError }>
<Plugin />
</PluginErrorBoundary>
</PluginContextProvider>
) ) }
</div>
);
}

export default PluginArea;
38 changes: 17 additions & 21 deletions packages/plugins/src/components/test/plugin-area.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: () => <ComponentSpy content="scoped" />,
icon: 'smiley',
scope: 'my-app',
} );
registerPlugin( 'scoped', {
render: () => <ComponentSpy content="scoped" />,
icon: 'smiley',
scope: 'my-app',
} );

render( <PluginArea scope="my-app" /> );
render( <PluginArea scope="my-app" /> );

act( () => {
registerPlugin( 'unscoped', {
render: () => <TestComponent content="unscoped" />,
icon: 'smiley',
} );
act( () => {
registerPlugin( 'unscoped', {
render: () => <TestComponent content="unscoped" />,
icon: 'smiley',
} );
} );

// Any store update triggers setState and causes PluginArea to rerender.
expect( ComponentSpy ).toHaveBeenCalledTimes( 1 );
}
);
expect( ComponentSpy ).toHaveBeenCalledTimes( 1 );
} );
} );

0 comments on commit e34777f

Please sign in to comment.