Search code examples
wordpresswordpress-gutenberg

Making a WordPress Gutenberg Field "required"


How do we make a WordPress Gutenberg attribute a required attribute? Curious if a property or attribute parameter can be defined, either in block.json or Edit.js, to avoid writing custom logic for each attribute.

block.json:

"attributes": {
    "blockSubTitle": {
        "type": "string",
        "default": "OVERVIEW"
    },
    "blockImage": {
        "type": "string",
        "default": null
    },
    "blockImageId": {
        "type": "integer",
        "default": null
    },
    "topPadding": {
        "type": "boolean",
        "default": false
    }
}

Solution

  • Block attributes can be updated in the Edit() function by using WordPress Gutenberg controls like a TextControl for a string or NumberControl for integer and ToggleControl for boolean values.

    All of these controls render out standard HTML form inputs which accept the required attribute. By adding your own CSS className, you can gain greater styling control over the required inputs for user feedback.

    Eg. TextControl

    <TextControl
        label="Sub Title"
        help="Required"
        ...
        className="is-required" // or your own class name
        required // this makes the input required 
    />
    

    As there is no default style for *:required:invalid in the Editor stylesheet, by adding your own error styles in editor.scss, the User will know when the input is required or in error:

    editor.scss

    /* Add a red asterisk after label to indicate required field */
    .is-required * label::after{
        color:#cc1818;
        content:" *" 
    }
    
    *:required:invalid {
        border-color:#cc1818;
        
        &:focus{
            border-color:#cc1818;
            box-shadow:0 0 0 1px #cc1818;
        }
    }
    

    Depending on the purpose of the required block attributes, indicating the field is required and providing a safe default value in block.json may be enough. However, if the required attribute is critical (eg. could potentially break something), then dispatching error notices for invalid values and preventing the post content from saving is the next step. Below is a complete example for a TextControl:

    edit.js

    import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
    import { PanelBody, TextControl } from '@wordpress/components';
    import { useDispatch } from '@wordpress/data';
    import { store as noticesStore } from '@wordpress/notices';
    
    import './editor.scss';
    
    export default function Edit({ attributes, setAttributes }) {
        const { blockSubTitle } = attributes;
    
        const { createErrorNotice, removeNotice } = useDispatch(noticesStore);
    
        const setSubTitle = (value) => {
    
            /* String cannot be empty */
            if (value.length == 0) {
    
                /* Dispatch error notice */
                createErrorNotice('Sub title is required', {
                    id: 'subtitle-required',
                    isDismissible: false,
                });
    
                /* Prevent post content from saving and autosaving */
                wp.data.dispatch('core/editor').lockPostSaving('subtitle-required');
                wp.data.dispatch('core/editor').lockPostAutosaving('subtitle-required');
    
            } else {
                /* Save valid value */
                setAttributes({ blockSubTitle: value });
    
                /* Remove notice */
                removeNotice('subtitle-required');
    
                /* Unlock post */
                wp.data.dispatch('core/editor').unlockPostSaving('subtitle-required');
                wp.data.dispatch('core/editor').unlockPostAutosaving('subtitle-required');
            }
        }
    
        return (
            <div {...useBlockProps()}>
                <InspectorControls>
                    <PanelBody>
                        <TextControl
                            label="Sub Title"
                            help="Required"
                            value={blockSubTitle}
                            onChange={(value) => setSubTitle(value)}
                            className="is-required"
                            required
                        />
                    </PanelBody>
                </InspectorControls>
                <h2>{blockSubTitle}</h2>
            </div>
        );
    }
    

    The example edit.js combined with editor.scss when no value is given, renders: Input Error