Gutenberg - Multiple InnerBlocks in one block-type
Asked Answered
H

3

15

I am trying to make custom columns block since the wordpress default used by gutenberg is not what I need.

So i have looked up how it works, its uses InnerBlocks block with a layout definition, but there is no way to specify the html tag and the class for the columns so it's useless for me.

Then I have decided to loop out columns using map, which works fine, then i added inside each column the InnerBlocks component to allow inserting other blocks to the column, but the problem is that in each column the content of InnerBlocks is shared, so I have tried to set key property for each InnerBlock and column to be unique and their content is still shared (no i am not using shared block).

It looks like gutenberg is using the same instance of InnerBlocks in each column.

I am trying to build a block type where you can add dynamically columns and into each column add "cards" with some information.

To give some idea what am i doing, here is the return of the edit function:

<section className="infonav">
            <div className="infonav__container">
                <div>
                    <button onClick={onAddBox}>{__('Add column', 'zmg-blocks')}</button>
                </div>
                <div className="infonav__row">
                    {[...new Array(columns).keys()].map((item, index) => {
                        return (
                                <div className="infonav__row__col" key={"info_cols"+index}>
                                    <div>
                                        <button onClick={onRemoveBox.bind(index)}>
                                            {__('Remove', 'zmg-blocks')}
                                        </button>
                                    </div>
                                    <InnerBlocks key={"info_boxes"+index}/>
                                </div>
                        );
                    })}
                </div>
            </div>
        </section>

Thank you

Heraclid answered 27/6, 2018 at 22:41 Comment(0)
V
8

Seems like gutenberg Innerblocks can only be used once in a block

Note: A block can render at most a single InnerBlocks and InnerBlocks.Content element in edit and save respectively. To create distinct arrangements of nested blocks, create a separate block type which renders its own InnerBlocks and assign as the sole allowedBlocks type.

Source: https://github.com/WordPress/gutenberg/tree/master/packages/block-editor/src/components/inner-blocks

You need a create another custom block(for the column only) along with this block which also uses Innerblock inside it so that other blocks can be nested inside. You can make use of allowedBlocks to only allow your custom column in this block

Virgievirgil answered 12/1, 2019 at 4:46 Comment(2)
InnerBlocks is now part of the block-editor package, not the editor package. Thus, The correct URL is github.com/WordPress/gutenberg/tree/master/packages/…. Although it's not really recommended to link to master URLs as they can change.Laguna
How would you include that custom bock inside your main block? If you could illustrate your explanation, it would be really, really helpful :)Extort
R
4

I had the same problem and based on Swopnil Dangol's answer have created something like the following:

Parent block:

registerBlockType("my-plugin/parent-block", {

    title: "Parent block",

    // Amount of columns
    attributes: {
        columns: { type: "number", default: 3 },
    }

    edit(props) {
        const { attributes: { columns } } = props;

        const onChangeColumns = (newColumns) => {
            setAttributes({ columns: newColumns });
        };

        // Either go for the template route
        // (create an inner block template based on amount of columns)
        const template = Array.from(Array(columns).keys()).map((i) => [
            "my-plugin/custom-inner-block",
        ]);
      
        // Or go for the allowed blocks route
        const allowedBlocks = ["my-plugin/custom-inner-block"];

        return (
            <div {...useBlockProps()}>

                {/* Use RangeControl in the InspectorControls to 
                    control the number of columns. */}
                <InspectorControls key="setting">
                    <div class="block-editor-block-card">
                        <fieldset>
                            <legend className="blocks-base-control__label">
                                {__("Amount of columns", "gutenpride")}
                            </legend>
                            <RangeControl
                                label="Columns"
                                value={columns}
                                onChange={(value) => onChangeColumns(value)}
                                min={1}
                                max={3}
                            />
                        </fieldset>
                    </div>
                </InspectorControls>
    
                <InnerBlocks
                    template={activeTemplate}
                    templateLock="all"
                    allowedBlocks={allowedBlocks}
                />
            </div>
        )
    },

    save() {
        return (
            <div {...useBlockProps.save()}>
                <InnerBlocks.Content />
            </div>
        );
    },

    // ...
}

Custom inner block:

registerBlockType("my-plugin/custom-inner-block", {

    title: "Custom Inner Block",

    edit(props) {
        const { attributes } = props;
        const { title } = attributes;

        return (
            <div {...useBlockProps()}>
                <InnerBlocks/>
            </div>
        );
    },

    save() {
        return (
            <div {...useBlockProps.save()}>
                <InnerBlocks.Content />
            </div>
        );
    },

    // ...
});
Ridglea answered 27/1, 2022 at 22:38 Comment(0)
P
2

If you meant to add multiple gutenberg blocks via InnerBlock from 'block-editor' as shown below, there is no way to add multiple blocks yet:

const BLOCK_TEMPLATE = [
  ['image-slider', { width: 800, height: 400 }],
  ['menu'],
];

registerBlockType('peregrine/inner-block-demo', {

  title: 'Inner Block Demo',

  edit({ className }) {
    return (
      <div className={className}>
        <InnerBlocks
          template={BLOCK_TEMPLATE}
          templateLock="all"
        />
      </div>
    );
  },

  save() {
    return (
      <div>
        <InnerBlocks.Content />
      </div>
    );
  },
});

Multiple inner blocks have complexities that don't seem worth the effort, and in general it either hints at a block that should be broken down into multiple simpler blocks, or with block attributes (not children). Let us know how the above suggestion work for you and we could revisit.

You can follow the discussion here: https://github.com/WordPress/gutenberg/issues/6808

But, your code looks more like adding inner elements and that is possible.

So to clarify further, what you supply to registerBlockType function is not block. A gutenberg block a graceful shortcode, something like this:

<!-- wp:image -->
<figure class="wp-block-image"><img src="source.jpg" alt="" /></figure>
<!-- /wp:image -->

Or this:

<!-- wp:latest-posts {"postsToShow":4,"displayPostDate":true} /-->

First one is called static block because it has its content in it. Second one is called dynamic block because it is self closig block and has no content. Content will be retrived from the php callback you supplied when you register your block.

Gutenberg uses React to print visual representation of the block onto editor screen. edit method on settings object should return a react element, this element will be used to produce visual representation for the block in the editor. save method also should return react element, but this element will be rendered into static html and saved into database: <figure class="wp-block-image"><img src="source.jpg" alt="" /></figure>. Now, dynamic blocks has no return element since they return null, so there is no content in them, that's is why they are self closing.

When server responds a request, it will get the content stored in the database and runs it through parser to see if it is static or dynamic block. If block is static, it has its content in itself so that content will be returned. If it is dynamic, registered callback function will be invoked and its return value will be returned.

Now, to answer your question, save and edit functions should return react element. A react element has to have single root element, but inner elements can any regular html like this one:

<div>
  <h1>Hello world</h1>
  <p>This is a sentence</p>
</div>

This is for performance reasons. DOM operations are expensive, that's is why it has single entry point into the DOM. That is why it is very fast. It has its own DOM then on, an whole tree resides in memory. React will walk through its DOM tree when a change takes place and render only changed branch, will not paint whole tree with every little change.

Important detail here is the code above looks like html but it is not, it is jsx. React can not render html directly. Jsx will be transpiled into react element by a transpiler like babel.

There is also createElement method on React which can be used to create react element. wp re-exports this method.

You can render one react element in another one. Without learning react you can never fully grasp the power or the potential of blocks. React library is not that large. Once you grasp the idea behind it you can learn it enough to be productive in a week or two.

Piscatorial answered 29/1, 2019 at 18:54 Comment(1)
Thank you very well explained, I am familiar with react I develop in it, but in pure react this code would work without any problems (since i did repeater/collection forms a lot), but now I understood that in case how guttenberg uses react I need to create a separate component for infonav__row__colHeraclid

© 2022 - 2024 — McMap. All rights reserved.