最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Gutenberg: Block validation fails if editor is reloaded or post is edited. data.getBlocks(); is empty in save function

programmeradmin2浏览0评论

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>;
}

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>;
}
Share Improve this question edited Apr 17, 2020 at 7:29 Marc asked Apr 16, 2020 at 19:11 MarcMarc 6979 silver badges28 bronze badges 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 Commented 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 Commented 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 Commented Apr 17, 2020 at 11:54
Add a comment  | 

1 Answer 1

Reset to default 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 ....
}

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论