How to select block variation from BlockVariationPicker

The question:

I have a custom block that has block variations. If I make the scope of the variations “inserter”, they work as expected. But if I make the scope “block” and use BlockVariationPicker so the user chooses a variation upon adding a block (like they do when adding the core columns block), nothing happens when I click on a variation icon.

I assume it’s because I need onSelect added, but I’m not sure what to do.

Register block:

import { __ } from "@wordpress/i18n";

import { registerBlockType } from "@wordpress/blocks";

import "./style.scss";

import Edit from "./edit";
import save from "./save";

registerBlockType("create-block/variation", {
    edit: Edit,
    save,
});

My variations:

const variations = [
{
    name: "one-column",
    title: __("One Column"),
    description: __("One column"),
    icon: "palmtree",
    innerBlocks: [["core/paragraph"]],
    scope: ["inserter"],
},
{
    name: "two-columns",
    title: __("Two Column"),
    description: __("Two columns"),
    icon: "palmtree",
    // isDefault: true,
    innerBlocks: [["core/paragraph"], ["core/paragraph"]],
    scope: ["inserter"],
},
];

export default variations;

My edit.js

import { __ } from "@wordpress/i18n";

import {
    useBlockProps,
    __experimentalBlockVariationPicker as BlockVariationPicker,
} from "@wordpress/block-editor";

import variations from "./variations";

import "./editor.scss";

export default function Edit() {
    return (
        <div {...useBlockProps()}>
            <BlockVariationPicker
                variations={variations}
                label={__("Menu Columns")}
                instructions={__("Choose how many menu columns")}
            />
        </div>
    );
}

What do I need to add to make it so when the user clicks on the variation it’s entered in the editor? This is what it looks like when I add my block, but nothing happens when I click. How to select block variation from BlockVariationPicker

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

I assume it’s because I need onSelect added, but I’m not sure what to
do.

What do I need to add to make it so when the user clicks on the
variation it’s entered in the editor?

One way, is add a variation attribute to your block type, then change the attribute value in the onSelect callback of the variation picker. That in turn will instruct React to re-render the block (because the block state has changed), hence on that render, we no longer display the variation picker. So basically, the picker is “closed”. 🙂

See example below where the block type renders a simple button with two variations — with an icon, and without it.

// File: index.js

import { registerBlockType } from '@wordpress/blocks';
import {
    useBlockProps,
    __experimentalBlockVariationPicker as BlockVariationPicker,
} from '@wordpress/block-editor';
import { Button } from '@wordpress/components';

const variations = [{
    name: 'button-with-icon',
    title: 'Button + Text + Icon',
    icon: 'button',
    scope: [ 'block' ],
    attributes: { icon: 'heart' },
}, {
    name: 'button-without-icon',
    title: 'Button + Text only',
    icon: 'button',
    scope: [ 'block' ],
}];

registerBlockType( 'my-blocks/foo-button', {
    apiVersion: 2,
    title: 'Foo Button',
    category: 'formatting',
    attributes: {
        icon: {
            type: 'string',
            default: '',
        },
        variation: {
            type: 'string',
            default: '',
        },
    },
    edit( { attributes, setAttributes } ) {
        if ( ! attributes.variation ) {
            return (
                <div { ...useBlockProps() }>
                    <BlockVariationPicker
                        variations={ variations }
                        label="Button Variant"
                        onSelect={ ( variation = variations[0] ) => {
                            setAttributes( {
                                ...variation.attributes,
                                variation: variation.name,
                            });
                        }}
                    />
                </div>
            );
        }

        return (
            <div { ...useBlockProps() }>
                <Button
                    text="Foo Button"
                    icon={ attributes.icon }
                />
            </div>
        );
    },
    save: ( { attributes } ) => (
        <div { ...useBlockProps.save() }>
            <Button
                text="Foo Button"
                icon={ attributes.icon }
            />
        </div>
    ),
} );

However, in the case of inner blocks, you would want to use the same approach as used by the core Columns block (see source on GitHub), where it checks if the current block contains any inner blocks and if yes, the inner blocks are displayed; otherwise, the variation picker is then displayed.

Here’s a full working example you can try where I used the same variations in your variations.js file:

// File: index.js

import {
    registerBlockType,
    createBlocksFromInnerBlocksTemplate,
} from '@wordpress/blocks';
import {
    useBlockProps,
    __experimentalUseInnerBlocksProps as useInnerBlocksProps,
    store as blockEditorStore,
    __experimentalBlockVariationPicker as BlockVariationPicker,
    InnerBlocks,
} from '@wordpress/block-editor';
import { useDispatch, useSelect } from '@wordpress/data';

import variations from './variations';

// Note that you can do EditContainer( props ) or EditContainer( { attributes, etc } ).
function EditContainer() {
    const blockProps = useBlockProps();
    const innerBlocksProps = useInnerBlocksProps( blockProps, {
        //allowedBlocks: ALLOWED_BLOCKS,
        orientation: 'horizontal',
        renderAppender: false,
    } );

    return <div { ...innerBlocksProps } />;
}

function Placeholder( { clientId, setAttributes } ) {
    // Always set a default variation, particularly if allowing skipping the variation picker.
    const defaultVariation = variations[0];
    // Or do something like this, which selects the variation having "isDefault: true":
//  const defaultVariation = variations.filter( item => item.isDefault )[0] || variations[0];

    const { replaceInnerBlocks } = useDispatch( blockEditorStore );
    const blockProps = useBlockProps();

    return (
        <div { ...blockProps }>
            <BlockVariationPicker
                label="Section Variant"
                variations={ variations }
                onSelect={ ( variation = defaultVariation ) => {
                    if ( variation.attributes ) {
                        setAttributes( variation.attributes );
                    }
                    if ( variation.innerBlocks ) {
                        replaceInnerBlocks(
                            clientId,
                            createBlocksFromInnerBlocksTemplate(
                                variation.innerBlocks
                            ),
                            true
                        );
                    }
                } }
                allowSkip
            />
        </div>
    );
}

const Edit = ( props ) => {
    const { clientId } = props;
    const hasInnerBlocks = useSelect(
        ( select ) =>
            select( blockEditorStore ).getBlocks( clientId ).length > 0,
        [ clientId ]
    );
    const Component = hasInnerBlocks
        ? EditContainer // display the inner blocks
        : Placeholder;  // or the variation picker

    return <Component { ...props } />;
};

registerBlockType( 'my-blocks/foo-section', {
    apiVersion: 2,
    title: 'Foo Section',
    category: 'layout',
    edit: Edit,
    save: () => (
        <div { ...useBlockProps.save( { className: 'foo-section' } ) }>
            <InnerBlocks.Content />
        </div>
    ),
} );

So try that and just let me know if you have questions regarding the code. 🙂

Method 2

*This is a supplemental answer to the accepted answer, so to OP, take this as a supplemental vitamin to the main one as prescribed by the doctor… :p

So in reply to your comment:

Is it possible to have additional elements in there? I have RichText
and InspectorControls. From what I can tell, the return <div { ...innerBlocksProps } />; can’t have anything added to it.

Yes, it is possible:

  1. Firstly, define our imports:

    import {
        useBlockProps,
        __experimentalUseInnerBlocksProps as useInnerBlocksProps,
        store as blockEditorStore,
        __experimentalBlockVariationPicker as BlockVariationPicker,
        InnerBlocks,
        // add these lines:
        InspectorControls,
        RichText,
    } from '@wordpress/block-editor';
    // and also this:
    import { PanelBody, TextControl } from '@wordpress/components';
    
  2. Change the EditContainer() function so that it includes the InspectorControls and RichText elements — the inspector control (which in my code below, has just a plain simple text box) will be displayed in the sidebar, whereas the rich text will be placed above the inner blocks (as defined via the specific variation):

    function EditContainer() {
        const blockProps = useBlockProps();
        const innerBlocksProps = useInnerBlocksProps( {}, {
            //allowedBlocks: ALLOWED_BLOCKS,
            orientation: 'horizontal',
            renderAppender: false,
        } );
    
        // *This line is just for demonstration, and so does the onChange callback below.
        // In actual implementation, you'd want to set the data via the block attributes.
        const [ foo, setFoo ] = wp.element.useState( 'bar baz' );
    
        return (
            <div { ...blockProps }>
                <InspectorControls>
                    <PanelBody>
                        <TextControl
                            label="Foo Input"
                            value={ foo }
                            onChange={ ( value ) => setFoo( value ) }
                        />
                    </PanelBody>
                </InspectorControls>
    
                <RichText
                    value={ foo }
                    onChange={ ( value ) => setFoo( value ) }
                />
    
                <div { ...innerBlocksProps } />
            </div>
        );
    }
    

    So the main thing you need to know is, I use a div to wrap the inspector controls, rich text and also inner blocks. Therefore, I used the blockProps with that div and not useInnerBlocksProps.

  3. In the save function, though, it will be up to you on how you want to output the rich text and the input value (i.e. the value of “Foo Input” above).

And if everything went well, you’d see something like so:

How to select block variation from BlockVariationPicker

How to select block variation from BlockVariationPicker


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Comment