Gutenberg Block Deprecation

02 June 2021 TechnologyWordpress
Share with:

A huge source of annoyance is the above message. Usually a simple click is enough to resolve it. Not always. This usually happens when you change the HTML generated by the save function of your custom block. So let’s see how to avoid it.

The way to go is to create a deprecation. In order to explain – I’ll provide an example. Let’s say we have this simple custom block:

const { __ } = wp.i18n;
const { registerBlockType } = wp.blocks;

const {
  PlainText,
  URLInput,
} = wp.blockEditor;

const {
  Button,
  BaseControl
} = wp.components;

const blockAttributes = {
  boxTitle: {
    type: 'string',
    default: ''
  },
  linkText: {
    type: 'string',
  },
  linkHref: {
    type: 'string',
    default: '',
  }
};

registerBlockType('hswp/test-block', {
  title: __('TEST Box', 'testing'),
  icon: 'admin-links',
  category: 'modula-blocks',
  keywords: [
    __('test'), __('link'), __('box'),
  ],
  attributes: blockAttributes,

  edit: (props) => {
    const {
      attributes: {
        boxTitle,
        linkText,
        linkHref
      },
      setAttributes
    } = props;

    return (
      <div className={"test-block-box"}>
        <PlainText
          tagName="h3"
          placeholder='Box Title'
          value={boxTitle}
          onChange={boxTitle => {
            setAttributes({ boxTitle: boxTitle });
          }}
        />
        <PlainText
          tagName="a"
          placeholder='Link Text'
          value={linkText}
          onChange={linkText => {
            setAttributes({ linkText: linkText });
          }}
        />
        <URLInput
          placeholder='Link URL'
          value={linkHref}
          onChange={linkHref => {
            setAttributes({ linkHref: linkHref });
          }}
        />
      </div>
    );
  },

  save: (props) => {
    const {
      attributes: {
        boxTitle,
        linkText,
        linkHref,
      },
    } = props;

    return (
      <div className={`test-block`}>
        <a
          className="test-block__link"
          href={linkHref}
          rel="noopener noreferrer"
        >
          <div className="test-block__text">
            <h3 className="test-block__title">
              {boxTitle}
            </h3>
            <span className="test-block__link-text">{linkText}</span>
          </div>
        </a>
      </div>
    );
  },
});

It generally creates a simple block with a title and a link. An important detail is that I’ve placed the attribute definition outside of the registerBlockType method.

So we want to add a new field – text description. To do so first we must add textDescription it to the attributes.

...
  textDescription: {
    type: 'string',
    default: '',
  },
...

And then add it to both edit and save methods’ variable definitions

    const {
      attributes: {
        boxTitle,
        textDescription,
        linkText,
        linkHref
      },
      className,
      setAttributes
    } = props;

Then we add it to the editor:


        <PlainText
          tagName="p"
          placeholder='Text Description'
          value={textDescription}
          onChange={textDescription => {
            setAttributes({ textDescription: textDescription });
          }}
        />

And to the save method:


    return (
      <div className={`test-block ${className}`}>
        <a
          className="test-block__link"
          href={linkHref}
          rel="noopener noreferrer"
        >
          <div className="test-block__text">
            <h3 className="test-block__title">
              {boxTitle}
            </h3>
            <p className='test-block__description'>{textDescription}</p>
            <span className="test-block__link-text">{linkText}</span>
          </div>
        </a>
      </div>
    );

If we save the block like this – it will trigger an error in the editor. This is because when opening the editor – Gutenberg checks every block and if its HTML differs from what the save() method generates – it causes an error. A way to do it is to use deprecation. Deprecation is an array of deprecation objects. When Gutenberg encounters such error – it checks for deprecations and triest to generate the code in each of them, until it finds one matching the current HTML. Generally the depraction must match the previous version of the save method:

import omit from 'lodash/omit';
....
deprecated: [
    {
      attributes: {...blockAttributes},
      migrate: (attributes) => {
        return omit(attributes, 'textDescription');
      },
      save: (props) => {
        const {
          attributes: {
            boxTitle,
            linkText,
            linkHref,
          },
          className,
        } = props;
        return (
          <div className={"test-block"}>
            <a
              className="test-block__link"
              href={linkHref}
              rel="noopener noreferrer"
            >
              <div className="test-block__text">
                <h3 className="test-block__title">
                  {boxTitle}
                </h3>
                <span className="test-block__link-text">{linkText}</span>
              </div>
            </a>
          </div>
        );
      },
    }
  ],

Few things I would like to mention – first – we must import the omit library. Next we must use it to remove the inheritant “textDescription” attribute, as it’s not used in the block. The result – the block loads successfully and we can add the new value.

But wait, there’s more – what if right after you’ve committed your changes – the customer, full of ideas as usual has decided that it would look majestic if we add image to this wonderful block? Well – yeah, I know. What we need to do is obviously add few more properties to the edit and save methods, but after that – we need to add a new deprecation object, above the previous.
I’ll not go in details over how to add an image upload – there’s plenty of data on it. Our save method will look like this:

    return (
      <div className={`test-block`} style={{ backgroundImage: `url("${mediaUrl}")` }}>
        <a
          className="test-block__link"
          href={linkHref}
          rel="noopener noreferrer"
        >
          <div className="test-block__text">
            <h3 className="test-block__title">
              {boxTitle}
            </h3>
            <p className="test-block__description">{textDescription}</p>
            <span className="test-block__link-text">{linkText}</span>
          </div>
        </a>
      </div>
    );

And then we add another deprecation object on top of the previous:

  deprecated: [
    {
      attributes: {...blockAttributes},
      migrate: (attributes) => {
        return omit(attributes, 'mediaUrl');
      },
      save: (props) => {
        const {
          attributes: {
            boxTitle,
            linkText,
            textDescription,
            linkHref,
          },
        } = props;

        return (
          <div className={`test-block`}>
            <a
              className="test-block__link"
              href={linkHref}
              rel="noopener noreferrer"
            >
              <div className="test-block__text">
                <h3 className="test-block__title">
                  {boxTitle}
                </h3>
                <p className="test-block__description">{textDescription}</p>
                <span className="test-block__link-text">{linkText}</span>
              </div>
            </a>
          </div>
        );
      },
    },
    {
....

Again, we omit the newly added mediaURL, and use the old version for the save.