1

I use the very same function to add some HTML to my block and post. Everything works fine until I edit the post or reload the editor: Block validation: Block validation failed for `simpletoc/toc.

Content generated by `save` function:

<p class="wp-block-simpletoc-toc"><h2 class="simpletoc-title">Table of Contents</h2><ul class="simpletoc"></ul></p>

Content retrieved from post body:

<p class="wp-block-simpletoc-toc"><h2 class="simpletoc-title">Table of Contents</h2><ul class="simpletoc"><li><a href="#sdfsfsdf">sdfsfsdf</a></li><li><a href="#sdfsdfsdfsdfdsf">sdfsdfsdfsdfdsf</a></li></ul></p>

So i tried to debug this. It seem that on reload the save function does not let me retrieve any blocks. The const blocks = data.getBlocks(); is empty in the save function. Why does this happen? It seems all so easy and it finally seem to work....

 registerBlockType( 'simpletoc/toc', {
        title: __( 'SimpleTOC', 'simpletoc' ),
        icon: listulicon,
        category: 'layout',
        edit: function ( props ) {
            return buildTOC( props );
        },
        save: function ( props ) {
            return buildTOC( props );
        },
    } );

function buildTOC(props){

    const data = wp.data.select( 'core/block-editor' );
    const blocks = data.getBlocks();
    const headings = blocks.map( ( item, index ) => {
        if ( item.name === 'core/heading' ) {
            var slug = '';
            var hashslug = '';
            var blockId = ( item.clientId );

            slug = item.attributes.content;
            hashslug = '#' + slug;

            return <li><a href={hashslug}>{item.attributes.content}</a></li>;
        }
    } );

    return <p className={ props.className }>
            <h2 class="simpletoc-title">{ __( 'Table of Contents', 'simpletoc' ) }</h2>
            <ul class="simpletoc">
                    {headings}
            </ul>
            </p>;
}
3
  • 1
    You shouldn't be updating other blocks inside your edit and save methods, the issue you're having is because what it has, and what it's given don't match. A block should always be the same when given the same props. I'd also advise using dependency injection, you shouldn't be fetching the blocks inside buildTOC, you should be passing it as a parameter
    – Tom J Nowell
    Apr 16, 2020 at 20:40
  • I removed the update of the blocks in my example above. Same problem. What do you mean by attributes? What is the best practise?
    – Marc
    Apr 17, 2020 at 7:30
  • your props, if I give your block A, B and C, expecting D, I should always get D regardless of the circumstances. If while I'm performing the check, the block changes its mind and modifies things to produces E, then something has gone terribly wrong and the fundamental assumptions are no longer correct
    – Tom J Nowell
    Apr 17, 2020 at 11:54

1 Answer 1

1

Put simply, your block misbehaves.

Given a set of parameters, the block should always give you the same result. The editor uses this as a check, and if the result does not match, then something is wrong and the blocks implementation has a bug.

In your blocks case, this is because when generating the markup, you fetch the data from scratch each and every time. As a result, if I pass it A, B, and C, I might get D, or I might get E, because your save function relies on external API calls and is no longer safely contained. You're not using the props/attributes to generate the output, you're using some other things. This is bad!

So instead, when you figure out what the TOC are supposed to be, store it in an attribute. You'll need to declare the attribute exists, and use the setter it provides you.

This leads us to a new problem:

function buildTOC(props){

    const data = wp.data.select( 'core/block-editor' );
    const blocks = data.getBlocks();
    const headings = blocks.map( ( item, index ) => {
        if ( item.name === 'core/heading' ) {
            var slug = '';
            var hashslug = '';
            var blockId = ( item.clientId );

            slug = item.attributes.content;
            hashslug = '#' + slug;

            return <li><a href={hashslug}>{item.attributes.content}</a></li>;
        }
    } );

The function that figures out which headings are in the document, is the same function that builds the final components. The function does too much, doesn't use dependency injection, and as a result, there is no separation of concerns. This needs to be refactored into 2 functions, and I would also suggest replacing your function, with a component e.g. something similar to this:

        edit: function ( props ) {
            const headings = find_headings( props );
            props.setAttributes( { headings: headings } );
            return <toc {...props} />;
        },
        save: function ( props ) {
            return <Toc  {...props} />;
        },

Where <Toc> is something like this:


function Toc( props ) {
    return ....
}
4
  • Puh, I hope I understood everything. I will try to impelement this hints!
    – Marc
    Apr 17, 2020 at 17:48
  • Sorry, one question. I don't get the component part. If I do something like this: function toc( props ) { return <p className={ props.className }> <h2 class="simpletoc-title">{ __( 'Table of Contents', 'simpletoc' ) }</h2> <ul class="simpletoc"> <li>Hello World</li> </ul> </p>; } I only get this: <toc class="wp-block-simpletoc-toc"></toc> How does this work? I found nothing on google about component and react that fits this pattern,
    – Marc
    Apr 17, 2020 at 19:35
  • I believe the issue is I suggested toc rather than Toc, I've updated the answer, for more info on components: reactjs.org/docs/components-and-props.html
    – Tom J Nowell
    Apr 17, 2020 at 20:23
  • 1
    Thank you! It works now. I hope this was my last question. Thank you so much!
    – Marc
    Apr 17, 2020 at 21:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.