Limiting the block count for InnerBlocks

One of the most versatile components that Gutenberg block developers have is the <InnerBlocks/> component. This component allows other blocks to be inserted into it. WordPress Core ships with the Group and Columns blocks that both use InnerBlocks internally to allow the creation of very complex block structures.

The InnerBlocks component solves some interesting problems for custom blocks, for example creating a list of ordered items. By leveraging InnerBlocks, one can skip having to build a bespoke system and rely on Gutenberg to handle the insertion, deletion, and ordering of items. It is very flexible and easy to use. However, while working with it I discovered that there is no built-in way to limit the number of blocks that can be inserted.

The renderAppender prop will allow us to solve this issue

RenderAppender Overview

Before we talk about the solution, let briefly discuss the renderAppender prop and how it works.

Released with WordPress. 5.3, the renderAppender prop allows us to control the interface for inserting blocks into the related <InnerBlocks/> component.

There are two predefined appenders available in WordPress core:

  1. <InnerBlocks.DefaultBlockAppender />
  2. <InnerBlocks.ButtonBlockAppender />

<InnerBlocks.DefaultBlockAppender />

As the name indicates, this is the default appender. If the renderAppender prop is undefined, then this is the appender that is used. It automatically inserts whichever block is configured as the default block via wp.blocks.setDefaultBlockName – which by default is the Paragraph block.

If you’ve worked in the block editor at all you’ve already seen this.

Look familiar?

So when working with <InnerBlocks/>, you don’t need to pass anything to renderAppender to get this style of appender. If fact, you can leave it off entirely.

<InnerBlocks />

<InnerBlocks.ButtonBlockAppender />

This appender is displayed as a + ( plus ) icon button that, when clicked, displays the block inserter. This appender does not insert a default block.

This appender is seen in the Group or Columns block.

Groups and Column blocks use InnerBlocks.ButtonBlockAppender

To use this in your <InnerBlocks/>, you have to pass a function to renderAppender that returns the <InnerBlocks.ButtonBlockAppender /> component.

<InnerBlocks
    renderAppender={ () => (
        <InnerBlocks.ButtonBlockAppender />
     ) }
/>

No appender at all

The last option is to pass false to renderAppender to not display any appender at all.

<InnerBlocks
    renderAppender={false}
/>

Solution

Now that we have an understanding of how the renderAppender prop works, let us use it to solve the problems of limiting the number of blocks allowed.

After some experimentation, the actual solution is fairly straight forward, we just need a way to count how many blocks have been inserted and if it’s more than we want, we set renderAppender to false so that nothing is displayed and the user cannot insert any more blocks until one is removed.

The core/block-editor datastore provides a getBlock() selector that we can use to get the information we need. We pass it the clientId of our block and we can get the list of innerBlocks, count them, and either display the appender or return false.

Below is the code that you would place into the edit property of a block, it’s very simple and but demonstrates the the solution.

import { useSelect } from '@wordpress/data';
import { InnerBlocks } from '@wordpress/block-editor';

const InnerBlockWithLimit = ( props ) => {
	const { clientId } = props;
	const innerBlockCount = useSelect( ( select ) => select( 'core/block-editor' ).getBlock( clientId ).innerBlocks );

	const appenderToUse = () => {
		if ( innerBlockCount.length < 10 ) {
			return (
				<InnerBlocks.ButtonBlockAppender/>
			);
		} else {
			return false;
		}
	}

	return (
		<InnerBlocks
			renderAppender={ () => appenderToUse() }
		/>
	)
}

Have you seen any other issues when working with <InnerBlocks/> or want to discuss this approach? If so, leave a comment below!

2 thoughts on “Limiting the block count for InnerBlocks

  1. Thanks a lot, this has helped me better understand the renderAppender functionality of InnerBlocks. I have one strange behaviour in my custom block, which I would like to solve: I need a switch between a false state (for an initial block setup) and a visible renderAppender when the content of the block has loaded (to add further child blocks on demand). This would perfectly fit the solution you described. But as soon as I use the “DefaultBlockAppender” the small appender button is always visible, left aligned and about 80 px down from my last child block. Whereas when I completely omit the renderAppender prop in I get the normal behaviour of a centered appender button that is hidden by default and only visible when the cursor is next to the button. Using the “ButtonBlockAppender” works fine. Not sure if this is the intended behaviour or a small bug.

    1. Hi Jim! Thanks for your comment!

      If I am understanding your issue, that seems like expected behaviour. If you leave off the renderAppender prop, you will get the default one. That is why we have to explicitly set it to false in the example code.

Leave a Reply

Your email address will not be published. Required fields are marked *