Gutenberg Custom InnerBlocks Appender
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:
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)