How to output child block attributes on a parent block

I have a custom Gutenberg block that contains a list of InnerBlocks (all the same known block), and I am trying to output the title attribute from each of those blocks in a list on the parent block.

I'm trying to get values from the innerblocks and add them to an array attribute on the parent block,

This is a simplified version of the parent block edit.js

const Edit = ( props ) = {
    const { attributes: { childValuesArray }, setAttributes } = props;
    const blockProps = useBlockProps();
    const ALLOWED_BLOCKS = [ 'custom/child-block' ];

    const { clientId } = props;
    const { select } = wp.data;
    const parentBlock = select( 'core/block-editor' ).getBlocksByClientId( clientId )[ 0 ];
    const childBlocks = parentBlock.innerBlocks;

    childBlocks.map(block = {
        childValuesArray.push( block.attributes.title, block.clientId );
    });
    props.setAttributes({ 'childValuesArray': childValuesArray });

    return (
        div { ...blockProps }
            div className=block-content
                InnerBlocks allowedBlocks={ ALLOWED_BLOCKS } /
            /div
            aside
                {
                    childValuesArray.map( (block, index) = {
                        return (
                            p id={ block.clientId }
                                { block.title }
                            /p
                        )
                    })
                }
            /aside
        /div
    );
};

export default Edit;

The issue I'm having is that the code to grab the child block values runs continuously, so the array of values keeps getting bigger.

Research suggests that I should maybe be using withDispatch to avoid the code running every time, but the documentation is rather brief, and I haven't been able to work out how to do this correctly.

Is withDispatch the right approach here? If so, how do I go about using it? If not, what's a better approach?

Topic block-editor Wordpress

Category Web


It's worth a mention that storing child block attributes in the parent block's attributes is a bit of a bizarre practice in isolation - ideally blocks should be completely independent of one-another. But if some functionality necessitates that a parent block access a child block's attributes, it would likely have a much smaller potential for headaches down the road to just read the attributes from the child blocks as necessary rather than duplicating the data into parent block attributes.

In short, you need to create a subscription which will re-render the component when changes occur in editor state by using either the useSelect() hook or the withSelect() HOC factory (I've written a brief introduction to the concepts behind these functions over here).

While a subscription to getBlocksByClientId( parent_block_client_id ) would be perfectly functional as the parent block's state becomes "dirty" when any change is made is made to a child block, I think a getBlocks( parent_block_client_id ) selector is a little cleaner as you don't have to traverse down to the innerBlocks array, and additionally the subscription won't trigger a re-render when any modification is made to the parent block.

In order to reduce overhead, it would be a good practice to place your logic which updates your attributes in a useEffect() hook with a dependency on the child block list such that it only occurs when the child blocks change.

import { useBlockProps, InnerBlocks } from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
import { useEffect } from '@wordpress/element';

const Edit = ( { attributes, clientId, setAttributes, ...props } ) => {
  const { child_values } = attributes;
  const child_blocks = useSelect(
    select => select( 'core/block-editor' ).getBlocks( clientId )
  );

  useEffect(
    () => {
      const child_values = child_blocks.map(
        ( { clientId, attributes: { title } } ) => ( { clientId, title } )
      );
      setAttributes( { child_values } );
    },
    [ child_blocks ]
  );

  return (
    <div {...useBlockProps()}>
      <div className="block-content">
        <InnerBlocks />
      </div>
      <aside>
      {child_values.map( ( { clientId, title }, index ) => (
        <p id={ clientId } key={ index }>{title}</p>
      ))}
      </aside>
    </div>
  );
}

export default Edit;

About

Geeks Mental is a community that publishes articles and tutorials about Web, Android, Data Science, new techniques and Linux security.