As the title states I am teaching myself how to create a custom Gutenburg Block for wordpress development and I have written the following code. It functions correctly when you save, but when you reload the saved page you get console errors and it shows

This block contains unexpected or invalid content.

When you click resolve it shows the following:

Resolve block

//  Import CSS.
import './editor.scss';
import './style.scss';

const { __ } = wp.i18n; // Import __() from wp.i18n
const { registerBlockType } = wp.blocks; // Import registerBlockType() from wp.blocks
const { RichText } = wp.blockEditor
const { withColors } = wp.blockEditor
const { PanelColorSettings} = wp.blockEditor;
const { InspectorControls } = wp.blockEditor;
const { PanelBody } = wp.components;


registerBlockType( 'cgb/block-my-block', {

    title: __( 'my-block - CGB Block' ), 
    icon: 'shield', 
    category: 'common', 
    keywords: [
        __( 'my-block — CGB Block' ),
        __( 'CGB Example' ),
        __( 'create-guten-block' ),
    ],
    attributes: {
        align: {
            type: 'string',
            default: 'full',
        },
        link_text: {
            selector: 'a', 
            source: 'children',  
        },
        link_url: {
                selector: 'a',  
                source: 'attribute', 
                attribute: 'href', 
        },
        txtColor: {
            type: 'string'
        },
        bgcolor: {
            type: 'string'
        },
    },
    supports: {
        align:true,
        //align: ['wide','full'], // limit only to these
    },

    getEditWrapperProps() {
        return {
            'data-align': 'full',
        };
    },

    edit: ( props ) => {

        let link_text = props.attributes.link_text 
        let link_url = props.attributes.link_url 
        let txtColor = props.attributes.txtColor
        let bgColor = props.attributes.bgColor

        function onChangeContentURL ( content ) {
            props.setAttributes({link_url: content})
        }

        function onChangeContentName ( content ) {
                props.setAttributes({link_text: content})
        }    
        
        function onChangeBGColor ( content ) {
            props.setAttributes({bgColor: content})
        }  

        function onChangeColor ( content ) {
            props.setAttributes({txtColor: content})
        }    

        return (
            
            
            <div className={ props.className } style={{ backgroundColor:bgColor, color: txtColor }}>


            <InspectorControls key= { 'inspector' } >
                    <PanelBody>

                    <PanelColorSettings 
                        title={ __('Title Color', 'tar') }
                        colorSettings= { [ 
                            {
                            value: txtColor,
                            onChange: (colorValue) => onChangeColor ( colorValue ),
                            label: __('Color', 'tar'),
                            },
                         ] }
                    />

                    <PanelColorSettings 
                        title={ __('Background Color', 'tar') }
                        colorSettings= { [ 
                            {
                            value: bgColor,
                            onChange: (colorValue) => onChangeBGColor ( colorValue ),
                            label: __('Color', 'tar'),
                            },
                         ] }
                    />

                </PanelBody>
            </InspectorControls>


                <p>Sample Link Block</p>
                <label>Name:</label>
                <RichText
                        className={props.className} // Automatic class: gutenberg-blocks-sample-block-editable
                        onChange={onChangeContentName} // onChange event callback
                        value={link_text} // Binding
                        placeholder="Name of the link"
                />
                <label>URL:</label>
                <RichText
                        format="string"             // Default is 'element'. Wouldn't work for a tag attribute
                        className={props.className} // Automatic class: gutenberg-blocks-sample-block-editable
                        onChange={onChangeContentURL} // onChange event callback
                        value={link_url} // Binding
                        placeholder="URL of the link"
                />   
                <p>— Hello from the backend.!!</p>

            </div>
        );
    },

    /**
     * The save function defines the way in which the different attributes should be combined
     * into the final markup, which is then serialized by Gutenberg into post_content.
     *
     * The "save" property must be specified and must be a valid function.
     *
     * @link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
     *
     * @param {Object} props Props.
     * @returns {Mixed} JSX Frontend HTML.
     */
    save: ( props ) => {
        let txtColor = props.attributes.txtColor
        let bgColor = props.attributes.bgColor

        return (
            <div className={ props.className } style={{ backgroundColor:bgColor, color:txtColor }} >
                <p>— Hello from the frontend.</p>
                <a href={props.attributes.link_url}>{props.attributes.link_text}</a>
                </div>
        );
    },
} );

The console error looks like the POST and the SAVE data are different causing the error.

The message is:

Block validation: Block validation failed for `cgb/block-my-block` ({name: "cgb/block-my-block", icon: {…}, attributes: {…}, keywords: Array(3), save: ƒ, …}).

Content generated by `save` function:

<div class="wp-block-cgb-block-my-block alignfull" style="color:#000000"><p>— Hello from the frontend.</p><a></a></div>

Content retrieved from post body:

<div class="wp-block-cgb-block-my-block alignfull" style="background-color:#cd2653;color:#000000"><p>— Hello from the frontend.</p><a></a></div>

So it looks to me as if the problem is with the Save function style tag's on the root element.

<div className={ props.className } style={{ backgroundColor:bgColor, color:txtColor }} >

I have removed one style only leaving the other and it works. Putting the other back in and it breaks. Have I included this multiple style wrong? If so what is the convention for adding multiple styles that save on the root element? Also I am new and I am learning from tutorials and reading the Gutenburg github docs. If there is something rudimentary I am doing wrong please let me know.

1

There are 1 best solutions below

1
On BEST ANSWER

The block validation issue is caused by a small typo where your attribute bgcolor (case sensitive) is called as bgColor in edit() and save().

Your code shows you are on the right path with creating your own custom Gutenberg block, so I'd like to share a suggestion to use array destructuring with props to make your code much easier to read and maintain. Your custom onChange functions which just call setAttributes()can also be removed in favor of calling setAttributes directly, this reduces how much code you need to write and reduces the chance of typos too..

Eg:

//  Import CSS.
import './editor.scss';
import './style.scss';

const { __ } = wp.i18n; // Import __() from wp.i18n
const { registerBlockType } = wp.blocks; // Import registerBlockType() from wp.blocks
const { RichText } = wp.blockEditor
const { withColors } = wp.blockEditor
const { PanelColorSettings } = wp.blockEditor;
const { InspectorControls } = wp.blockEditor;
const { PanelBody } = wp.components;


registerBlockType('cgb/block-my-block', {

title: __('my-block - CGB Block'),
icon: 'shield',
category: 'common',
keywords: [
    __('my-block — CGB Block'),
    __('CGB Example'),
    __('create-guten-block'),
],
attributes: {
    align: {
        type: 'string',
        default: 'full',
    },
    link_text: {
        selector: 'a',
        source: 'children',
    },
    link_url: {
        selector: 'a',
        source: 'attribute',
        attribute: 'href',
    },
    txtColor: {
        type: 'string',
    },
    bgColor: {
        type: 'string',
    },
},
supports: {
    align: true,
    //align: ['wide','full'], // limit only to these
},

getEditWrapperProps() {
    return {
        'data-align': 'full',
    };
},
// Use array destructuring of props
edit: ({ attributes, className, setAttributes } = props) => {

    // Use array destructuring of the attributes
    const { link_text, link_url, txtColor, bgColor } = attributes;
    
    // Removed custom onChange functions that just call setAttributes

    return (

        <div className={className} style={{ backgroundColor: bgColor, color: txtColor }}>

            <InspectorControls key={'inspector'} >

                <PanelBody>

                    <PanelColorSettings
                        title={__('Title Color', 'tar')}
                        colorSettings={[
                            {
                                value: txtColor,
                                onChange: (colorValue) => setAttributes({ txtColor: colorValue }),
                                label: __('Color', 'tar'),
                            },
                        ]}
                    />

                    <PanelColorSettings
                        title={__('Background Color', 'tar')}
                        colorSettings={[
                            {
                                value: bgColor,
                                onChange: (colorValue) => setAttributes({ bgColor: colorValue }),
                                label: __('Color', 'tar'),
                            },
                        ]}
                    />

                </PanelBody>

            </InspectorControls>

            <p>Sample Link Block</p>
            <label>Name:</label>
            <RichText
                className={className} // Automatic class: gutenberg-blocks-sample-block-editable
                onChange={(content) => setAttributes({ link_text: content })} // onChange event callback
                value={link_text} // Binding
                placeholder="Name of the link"
            />
            <label>URL:</label>
            <RichText
                format="string" // Default is 'element'. Wouldn't work for a tag attribute
                className={className} // Automatic class: gutenberg-blocks-sample-block-editable
                onChange={(content) => setAttributes({ link_url: content })} // onChange event callback
                value={link_url} // Binding
                placeholder="URL of the link"
            />
            <p>— Hello from the backend.!!</p>

        </div>
    );
},

/**
 * The save function defines the way in which the different attributes should be combined
 * into the final markup, which is then serialized by Gutenberg into post_content.
 *
 * The "save" property must be specified and must be a valid function.
 *
 * @link https://wordpress.org/gutenberg/handbook/block-api/block-edit-save/
 *
 * @param {Object} props Props.
 * @returns {Mixed} JSX Frontend HTML.
 */
// Use array destructuring of props
save: ({ attributes, className } = props) => {

    // Use array destructuring of the attributes
    const { txtColor, bgColor, link_url, link_text } = attributes;

    return (
        <div className={className} style={{ backgroundColor: bgColor, color: txtColor }}>
            <p>— Hello from the frontend.</p>
            <a href={link_url}>{link_text}</a>
        </div >
    );
},
});