Why is my Gutenberg block returning an error after initial save?

I created a Gutenberg block but after saving it and going back into the post I receive this error:

This block contains unexpected or invalid content

I couldn’t find any inconsistencies and I had a friend also take a look over to see if there was anything amiss. What am I doing wrong?

registerBlockType('myblog/check-list', {
    //Built-in Attributes
    title: 'Check List',
    description: 'Generate a bulleted list with green checkmarks',
    icon: 'list-view',
    category: 'design',

    //Custom Attributes
    attributes: {
        title: {
            type: 'string',
            source: 'html',
            selector: 'h2'
        },
        text: {
            type: 'string',
            source: 'html',
            selector: 'p'
        },
        titleColor: {
            type: 'string',
            default: '#383838'
        },
        checkListItemOne: {
            type: 'string',
            source: 'html',
            selector: 'span'
        },
        checkListItemTwo: {
            type: 'string',
            source: 'html',
            selector: 'span'
        }
    },

    //Built-in Functions
    edit({attributes, setAttributes}) {
        const{
            title,
            text,
            titleColor,
            checkListItemOne,
            checkListItemTwo,
        } = attributes;

        //Custom Functions
        
        function onChangeTitle(newTitle) {
            setAttributes( { title: newTitle } );
        }

        function onChangeText(newText) {
            setAttributes( { text: newText } );
        }

        function onTitleColorChange(newColor){
            setAttributes( { titleColor: newColor } );
        }

        function onChangeCheckListItemOne(newListItemOne) {
            setAttributes( { checkListItemOne: newListItemOne})
        }

        function onChangeCheckListItemTwo(newListItemTwo) {
            setAttributes( { checkListItemTwo: newListItemTwo})
        }
        

        return ([
            <InspectorControls style={ { marginBottom: '40px' } }>
                <PanelBody title={ 'Headline Color' }>
                    <p><strong>Choose Title Color</strong></p>
                    <ColorPalette 
                        value={titleColor} 
                        onChange={onTitleColorChange} 
                    />
                </PanelBody>
            </InspectorControls>,

            <div class="col-md-5 offset-md-1">
                <div class="green-check-list-container">
                    <RichText 
                        key="editable"
                        tagName="h2"
                        placeholder="Headline for check list" 
                        value= { title }
                        onChange= { onChangeTitle }
                        style= { { color: titleColor } }
                    />
                    <RichText 
                        key="editable"
                        tagName="p"
                        placeholder="Additional context for the list" 
                        value= { text }
                        onChange= { onChangeText }
                    />
                    <ul class="green-check-list-items">
                        <li>
                            <RichText 
                                key="editable"
                                tagName="span"
                                placeholder="List Item" 
                                value= { checkListItemOne }
                                onChange= { onChangeCheckListItemOne }
                            />
                        </li>                        
                        <li>
                            <RichText 
                                key="editable"
                                tagName="span"
                                placeholder="List Item" 
                                value= { checkListItemTwo }
                                onChange= { onChangeCheckListItemTwo }
                            />
                        </li>
                    </ul>
                </div>
            </div>
        ]);
    },

    save({ attributes }) {
        const {
            title,
            text,
            titleColor,
            checkListItemOne,
            checkListItemTwo,
        } = attributes;

        return (
            <div class="col-md-5 offset-md-1">
                <div class="green-check-list-container">
                    <h2 style={ { color: titleColor } }>{title}</h2>
                    <p>{ text }</p>
                    <ul class="green-check-list-items">
                        <li>
                            <span>{ checkListItemOne }</span>
                        </li>
                        <li>
                            <span>{ checkListItemTwo }</span>
                        </li>
                    </ul>
                </div>
            </div>
        );
    }
});

2 Answers
2

The main issue in your code is with these two attributes which use the exact same selector (the first span element in your block content):

checkListItemOne: {
    type: 'string',
    source: 'html',
    selector: 'span' // same as below
},
checkListItemTwo: {
    type: 'string',
    source: 'html',
    selector: 'span' // same as above
}

Which means the block editor will always use the content of the first span element in the saved block content, which then results in a “block validation failed” error like so:

enter image description here

Therefore, make sure that you set the proper selector value for your block attributes. Here’s an example using the CSS selectors element > element and :nth-child(n):

checkListItemOne: {
    type: 'string',
    source: 'html',
    selector: 'li:nth-child(1) > span'
},
checkListItemTwo: {
    type: 'string',
    source: 'html',
    selector: 'li:nth-child(2) > span'
}

Additional Issues/Notes

  1. The block editor handbook says:

    HTML Formatting Tags Display in the Content

    If the HTML tags from text formatting such as <strong> or <em> are
    being escaped and displayed on the frontend of the site, this is
    likely due to an issue in your save function. Make sure your code
    looks something like <RichText.Content tagName="h2" value={ heading } /> (ESNext) within your save function instead of simply outputting
    the value with <h2>{ heading }</h2>.

    So for example in your case, you’d use this instead of <span>{ checkListItemOne }</span>:

    <RichText.Content tagName="span" value={ checkListItemOne } />
    
  2. If you look at the above screenshot, the block’s outmost wrapper has two class attributes, which results in the “Expected attribute class of value `…” notice:

    <div class="col-md-5 offset-md-1" class="wp-block-myblog-check-list">
    

    To fix that, use the className attribute and not class in your <div>:

    And although class does work (despite being a reserved keyword in JavaScript), the (current) React documentation actually says, “To specify a CSS class, use the className attribute.“, so you should use that attribute in all your <div> and other elements having a CSS class.

    return (
        <div className="col-md-5 offset-md-1">
            <div className="green-check-list-container">
                ... your code.
            </div>
        </div>
    );
    
  3. WordPress 5.6.0 uses block editor API version 2 which introduced a new hook named useBlockProps, so I suggest you to use it in your block’s edit and save callbacks. 🙂

    Example in your save callback:

    const blockProps = useBlockProps.save( { className: 'col-md-5 offset-md-1' } );
    
    return (
        <div { ...blockProps }>
            <div className="green-check-list-container">
                ... your code.
            </div>
        </div>
    );
    

Leave a Comment