Skip to content

Commit

Permalink
Lazy render block types in the inserter (#33749)
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowriad authored Jul 30, 2021
1 parent 4e99d1c commit f2295dc
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 173 deletions.
74 changes: 52 additions & 22 deletions packages/block-editor/src/components/inserter/block-types-tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { map, flow, groupBy, orderBy } from 'lodash';
*/
import { __, _x } from '@wordpress/i18n';
import { useMemo, useEffect } from '@wordpress/element';
import { useAsyncList } from '@wordpress/compose';

/**
* Internal dependencies
Expand All @@ -21,6 +22,14 @@ const getBlockNamespace = ( item ) => item.name.split( '/' )[ 0 ];

const MAX_SUGGESTED_ITEMS = 6;

/**
* Shared reference to an empty array for cases where it is important to avoid
* returning a new array reference on every invocation and rerendering the component.
*
* @type {Array}
*/
const EMPTY_ARRAY = [];

export function BlockTypesTab( {
rootClientId,
onInsert,
Expand Down Expand Up @@ -71,6 +80,24 @@ export function BlockTypesTab( {
// Hide block preview on unmount.
useEffect( () => () => onHover( null ), [] );

/**
* The inserter contains a big number of blocks and opening it is a costful operation.
* The rendering is the most costful part of it, in order to improve the responsiveness
* of the "opening" action, these lazy lists allow us to render the inserter category per category,
* once all the categories are rendered, we start rendering the collections and the uncategorized block types.
*/
const currentlyRenderedCategories = useAsyncList( categories );
const didRenderAllCategories =
categories.length === currentlyRenderedCategories.length;

// Async List requires an array
const collectionEntries = useMemo( () => {
return Object.entries( collections );
}, [ collections ] );
const currentlyRenderedCollections = useAsyncList(
didRenderAllCategories ? collectionEntries : EMPTY_ARRAY
);

return (
<InserterListbox>
<div>
Expand All @@ -85,7 +112,7 @@ export function BlockTypesTab( {
</InserterPanel>
) }

{ map( categories, ( category ) => {
{ map( currentlyRenderedCategories, ( category ) => {
const categoryItems = itemsPerCategory[ category.slug ];
if ( ! categoryItems || ! categoryItems.length ) {
return null;
Expand All @@ -106,7 +133,7 @@ export function BlockTypesTab( {
);
} ) }

{ uncategorizedItems.length > 0 && (
{ didRenderAllCategories && uncategorizedItems.length > 0 && (
<InserterPanel
className="block-editor-inserter__uncategorized-blocks-panel"
title={ __( 'Uncategorized' ) }
Expand All @@ -120,27 +147,30 @@ export function BlockTypesTab( {
</InserterPanel>
) }

{ map( collections, ( collection, namespace ) => {
const collectionItems = itemsPerCollection[ namespace ];
if ( ! collectionItems || ! collectionItems.length ) {
return null;
{ map(
currentlyRenderedCollections,
( [ namespace, collection ] ) => {
const collectionItems = itemsPerCollection[ namespace ];
if ( ! collectionItems || ! collectionItems.length ) {
return null;
}

return (
<InserterPanel
key={ namespace }
title={ collection.title }
icon={ collection.icon }
>
<BlockTypesList
items={ collectionItems }
onSelect={ onSelectItem }
onHover={ onHover }
label={ collection.title }
/>
</InserterPanel>
);
}

return (
<InserterPanel
key={ namespace }
title={ collection.title }
icon={ collection.icon }
>
<BlockTypesList
items={ collectionItems }
onSelect={ onSelectItem }
onHover={ onHover }
label={ collection.title }
/>
</InserterPanel>
);
} ) }
) }
</div>
</InserterListbox>
);
Expand Down
150 changes: 0 additions & 150 deletions packages/block-editor/src/components/inserter/test/block-types-tab.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import { sortBy, uniq } from 'lodash';
* @return {Promise} Promise resolving with an array containing all inserter item titles.
*/
export async function getAllBlockInserterItemTitles() {
// The inserter render lazy renders the list of blocks
// meaning we should wait for the browser to be completed idle.
// Ideally, we shouldn't use a timeout and instead check the browser is idle for
// a specific duration, but didn't manage to find a simple way to do that.
// eslint-disable-next-line no-restricted-syntax
await page.waitFor( 500 );

const inserterItemTitles = await page.evaluate( () => {
return Array.from(
document.querySelectorAll(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe( 'Register block type hooks', () => {
it( 'has a custom category for Paragraph block', async () => {
await openGlobalBlockInserter();

const widgetsCategory = await page.$(
const widgetsCategory = await page.waitForSelector(
'.block-editor-block-types-list[aria-label="Widgets"]'
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ describe( 'Inserting blocks', () => {

it( 'shows block preview when hovering over block in inserter', async () => {
await openGlobalBlockInserter();
await page.waitForSelector( '.editor-block-list-item-paragraph' );
await page.focus( '.editor-block-list-item-paragraph' );
const preview = await page.waitForSelector(
'.block-editor-inserter__preview',
Expand Down

0 comments on commit f2295dc

Please sign in to comment.