Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Editor: Use hooks instead of HoC in BlockManager #65349

Merged
merged 2 commits into from
Sep 14, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 58 additions & 72 deletions packages/editor/src/components/block-manager/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
* WordPress dependencies
*/
import { store as blocksStore } from '@wordpress/blocks';
import { withSelect, withDispatch } from '@wordpress/data';
import { useDispatch, useSelect } from '@wordpress/data';
import { SearchControl, Button } from '@wordpress/components';
import { __, _n, sprintf } from '@wordpress/i18n';
import { useEffect, useState } from '@wordpress/element';
import { useDebounce, compose } from '@wordpress/compose';
import { useEffect, useMemo, useState } from '@wordpress/element';
import { useDebounce } from '@wordpress/compose';
import { speak } from '@wordpress/a11y';
import { store as preferencesStore } from '@wordpress/preferences';

Expand All @@ -17,41 +17,70 @@ import { unlock } from '../../lock-unlock';
import { store as editorStore } from '../../store';
import BlockManagerCategory from './category';

function BlockManager( {
blockTypes,
categories,
hasBlockSupport,
isMatchingSearchTerm,
numberOfHiddenBlocks,
enableAllBlockTypes,
} ) {
export default function BlockManager() {
const debouncedSpeak = useDebounce( speak, 500 );
const [ search, setSearch ] = useState( '' );
const { showBlockTypes } = unlock( useDispatch( editorStore ) );

// Filtering occurs here (as opposed to `withSelect`) to avoid
// wasted renders by consequence of `Array#filter` producing
// a new value reference on each call.
blockTypes = blockTypes.filter(
( blockType ) =>
hasBlockSupport( blockType, 'inserter', true ) &&
( ! search || isMatchingSearchTerm( blockType, search ) ) &&
( ! blockType.parent ||
blockType.parent.includes( 'core/post-content' ) )
);
const {
blockTypes,
categories,
hasBlockSupport,
isMatchingSearchTerm,
numberOfHiddenBlocks,
} = useSelect( ( select ) => {
// Some hidden blocks become unregistered
// by removing for instance the plugin that registered them, yet
// they're still remain as hidden by the user's action.
// We consider "hidden", blocks which were hidden and
// are still registered.
const _blockTypes = select( blocksStore ).getBlockTypes();
const hiddenBlockTypes = (
select( preferencesStore ).get( 'core', 'hiddenBlockTypes' ) ?? []
).filter( ( hiddenBlock ) => {
return _blockTypes.some(
( registeredBlock ) => registeredBlock.name === hiddenBlock
);
} );

return {
blockTypes: _blockTypes,
categories: select( blocksStore ).getCategories(),
hasBlockSupport: select( blocksStore ).hasBlockSupport,
isMatchingSearchTerm: select( blocksStore ).isMatchingSearchTerm,
numberOfHiddenBlocks:
Array.isArray( hiddenBlockTypes ) && hiddenBlockTypes.length,
};
}, [] );

function enableAllBlockTypes( newBlockTypes ) {
const blockNames = newBlockTypes.map( ( { name } ) => name );
showBlockTypes( blockNames );
}

const filteredBlockTypes = useMemo( () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have taken this comment into account and cached the results, but is this a good idea?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original comment concerns returning a new array reference from the mapSelect callback. This would result in a rerender every time the block store is updated and a warning from useSelect in dev mode.

It should be okay to leave it without memoization. But don't have a strong opinion here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Just to be safe, I would like to remove useMemo and then merge.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good 👍

return blockTypes.filter(
( blockType ) =>
hasBlockSupport( blockType, 'inserter', true ) &&
( ! search || isMatchingSearchTerm( blockType, search ) ) &&
( ! blockType.parent ||
blockType.parent.includes( 'core/post-content' ) )
);
}, [ blockTypes, hasBlockSupport, isMatchingSearchTerm, search ] );

// Announce search results on change
useEffect( () => {
if ( ! search ) {
return;
}
const count = blockTypes.length;
const count = filteredBlockTypes.length;
const resultsFoundMessage = sprintf(
/* translators: %d: number of results. */
_n( '%d result found.', '%d results found.', count ),
count
);
debouncedSpeak( resultsFoundMessage );
}, [ blockTypes.length, search, debouncedSpeak ] );
}, [ filteredBlockTypes?.length, search, debouncedSpeak ] );

return (
<div className="editor-block-manager__content">
Expand All @@ -69,7 +98,9 @@ function BlockManager( {
<Button
__next40pxDefaultSize
variant="link"
onClick={ () => enableAllBlockTypes( blockTypes ) }
onClick={ () =>
enableAllBlockTypes( filteredBlockTypes )
}
>
{ __( 'Reset' ) }
</Button>
Expand All @@ -89,7 +120,7 @@ function BlockManager( {
aria-label={ __( 'Available block types' ) }
className="editor-block-manager__results"
>
{ blockTypes.length === 0 && (
{ filteredBlockTypes.length === 0 && (
<p className="editor-block-manager__no-results">
{ __( 'No blocks found.' ) }
</p>
Expand All @@ -98,64 +129,19 @@ function BlockManager( {
<BlockManagerCategory
key={ category.slug }
title={ category.title }
blockTypes={ blockTypes.filter(
blockTypes={ filteredBlockTypes.filter(
( blockType ) =>
blockType.category === category.slug
) }
/>
) ) }
<BlockManagerCategory
title={ __( 'Uncategorized' ) }
blockTypes={ blockTypes.filter(
blockTypes={ filteredBlockTypes.filter(
( { category } ) => ! category
) }
/>
</div>
</div>
);
}

export default compose( [
withSelect( ( select ) => {
const {
getBlockTypes,
getCategories,
hasBlockSupport,
isMatchingSearchTerm,
} = select( blocksStore );
const { get } = select( preferencesStore );

// Some hidden blocks become unregistered
// by removing for instance the plugin that registered them, yet
// they're still remain as hidden by the user's action.
// We consider "hidden", blocks which were hidden and
// are still registered.
const blockTypes = getBlockTypes();
const hiddenBlockTypes = (
get( 'core', 'hiddenBlockTypes' ) ?? []
).filter( ( hiddenBlock ) => {
return blockTypes.some(
( registeredBlock ) => registeredBlock.name === hiddenBlock
);
} );
const numberOfHiddenBlocks =
Array.isArray( hiddenBlockTypes ) && hiddenBlockTypes.length;

return {
blockTypes,
categories: getCategories(),
hasBlockSupport,
isMatchingSearchTerm,
numberOfHiddenBlocks,
};
} ),
withDispatch( ( dispatch ) => {
const { showBlockTypes } = unlock( dispatch( editorStore ) );
return {
enableAllBlockTypes: ( blockTypes ) => {
const blockNames = blockTypes.map( ( { name } ) => name );
showBlockTypes( blockNames );
},
};
} ),
] )( BlockManager );
Loading