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 Answer
Reset to default 1Put 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 ....
}
buildTOC
, you should be passing it as a parameter – Tom J Nowell ♦ Commented Apr 16, 2020 at 20:40