Gutenberg Custom InnerBlocks Appender

Published
Categories

The updated Block Editor that ships with WordPress 5.3 gives us the ability to define a custom control to add inner blocks to our custom blocks. This is great if you have limited which blocks are allowed in your parent block and do not need the full inserter interface.

As an example, I recently built a button block and accompanying button group block as part of a larger set of blocks for a new theme:

Button Group block with a custom append button

Since I limit my Button Group block to only allow Buttons, there’s no need for the full categories and search functionality provided by the default appender.

Setting up InnerBlocks

In addition to innerBlocks we also need to add the renderAppender prop and pass it a function that returns a control, in this case a simple button.

const ALLOWED_BLOCKS = ["tsg/button"];
const TEMPLATE = [["tsg/button"]];

<InnerBlocks
	allowedBlocks={ALLOWED_BLOCKS}
	template={TEMPLATE}
	templateLock={false}
	renderAppender={() => (
		<button
			className="tsg-c-button tsg-c-button--placeholder"
			onClick={insertButtonBlock}
		>
			Add
		</button>
	)}
/>Code language: JavaScript (javascript)

Check out the docs for InnerBlocks

Adding Functionality to Our Appender

Now that we have our custom button set up we need to have it actually insert a block. This is a pretty simple example since we know exactly which block needs to be added. The important part is extracting clientId from the object that gets passed to our main edit function and then creating a function that uses dispatch and select to programmatically insert the block that we want.

Using insertBlock we use our parent block’s clientId to tell the editor which block we want to add our new one to as well as the position we want to place it inside.

export default function ButtonGroupEdit({
	attributes,
	setAttributes,
	className,
	clientId,
	innerBlocks
}) {

	function insertButtonBlock() {
		const innerCount = select("core/editor").getBlocksByClientId(clientId)[0]
			.innerBlocks.length;
		let block = createBlock("tsg/button");
		dispatch("core/block-editor").insertBlock(block, innerCount, clientId);
	}

}Code language: JavaScript (javascript)

Keep in mind if you have several blocks you’d like to have available for your inner blocks, you will need to provide your own interface to allow users to select a specific block.

Putting it all together

Here is the completed edit function for my Button Group block with a custom appender:

/**
 * WordPress dependencies
 */
import { __ } from "@wordpress/i18n";
import { InnerBlocks, InspectorControls } from "@wordpress/block-editor";
import { createBlock } from "@wordpress/blocks";
import { dispatch, select } from "@wordpress/data";
import { Button, PanelBody, ToggleControl } from "@wordpress/components";

/**
 * External dependencies
 */
import classnames from "classnames";

const ALLOWED_BLOCKS = ["tsg/button"];
const TEMPLATE = [["tsg/button"]];

export default function ButtonGroupEdit({
	attributes,
	setAttributes,
	className,
	clientId,
	innerBlocks
}) {
	const { isCentered } = attributes;

	function insertButtonBlock() {
		const innerCount = select("core/editor").getBlocksByClientId(clientId)[0]
			.innerBlocks.length;
		let block = createBlock("tsg/button");
		dispatch("core/block-editor").insertBlock(block, innerCount, clientId);
	}
	
	const classBlockName = "tsg-c-button-group";

	let classes = {
		[`${classBlockName}`]: true,
		[`${classBlockName}--center`]: isCentered,
		className: undefined !== className
	};

	return (
		<div className={classnames(classes)}>
			<InspectorControls>
				<PanelBody title={__("Options", "tsg")}>
					<ToggleControl
						label={__("Centered style", "tsg")}
						checked={isCentered}
						onChange={() => {
							setAttributes({ isCentered: !isCentered });
						}}
					/>
				</PanelBody>
			</InspectorControls>
			<InnerBlocks
				allowedBlocks={ALLOWED_BLOCKS}
				template={TEMPLATE}
				templateLock={false}
				renderAppender={() => (
					<button
						className="tsg-c-button tsg-c-button--placeholder"
						type="button"
						onClick={insertButtonBlock}
					>
						Add
					</button>
				)}
			/>
		</div>
	);
}
Code language: JavaScript (javascript)