Extending Gutenberg group block: How to properly combine multiple attributes?

In order to modify an attribute, you should use the setAttributes() function as you do elsewhere in your code. This function updates the value in Gutenberg’s data stores, which triggers a re-render for any components depending on it and stages the change to be updated in the database when the post is saved.

        const { width } = props.attributes;
        const { color } = props.attributes;
        const { highlight } = props.attributes;
        
        const combo = width + ' ' + color + ' ' + highlight;
        
        if ( combo ) {
          props.attributes.className = `${ combo }`;
        }

Additionally, in this code the if( combo ) condition will always execute, as the combo assignment will always evaluate to a non-empty string which is “truthy” in JavaScript. In the case that width, color, and highlight are undefined, combo becomes the string "undefined undefined undefined", which seems undesirable for an HTML class name.

I think it would be a good idea to prefix each attribute-derived class name as well so you can distinguish them from one another, e.g. att-highlight-red instead of red. A function to convert an object of key/value pairs into class names and ignore undefined values would be useful to that end:

function attsToClassNames( atts = {} ) {
  const classes = [];

  for( const [ key, value ] in Object.entries( atts ) ) {
    if( ! value && value !== 0 ) // Ignore falsey values, except 0
      continue;

    classes.push( `att-${key}-${value}` );
  }

  return classes;
}

All of the above in mind, I’d replace the section of code referenced at the beginning of this answer with something like this:

const {
  attributes,
  setAttributes,
} = props;
const {
  width,
  color,
  highlight,
  className,
} = attributes;

// Retrieve the current attribute classes.
const attClasses = attsToClassNames( { width, color, highlight } ).join( ' ' );

// If the `className` attribute does not reflect the current attribute classes,
// update it.
if( className !== attClasses )
  setAttributes( { className: attClasses } );

Now, className should always reflect the most recent list of class names derived from the other attributes, and will be staged in Gutenberg’s data stores to be saved along with the rest of the post & block attributes.

In order to make this (very slightly) more efficient, we can leverage React’s useEffect() hook (imported from @wordpress/element) and specify the relevant attributes in the “dependency array” – this will make it so that rebuilding the attribute classes only occurs on the very first render, then only if one or more of those attributes change after that:

useEffect(
  () => {
    const attClasses = attsToClassNames( { width, color, highlight } ).join( ' ' );
    
    if( className !== attClasses )
      setAttributes( { className: attClasses } );
  },
  [ width, color, highlight ]
);

This may still pose some other potential issues depending on your specific use-case – namely if you’d like to incorporate any other external className like that which is passed in on props.

Leave a Comment