Gutenberg: Block SelectControl saves attributes but after a reload of Gutenberg it loads its defaults. Why?

I thought I had it. I added an attribute “max_level” and a SelectControl element “SelectMaxLevel”. Everything works according to plan: I can set the max level and it is saved in the block. When I save the post and display the post see that the php part uses the “maximum level” variable. But when I edit the post the blocks loads it’s default value. Why does this happen?

registerBlockType('simpletoc/toc', {
  title: __('SimpleTOC', 'simpletoc'),
  icon: simpletocicon,
  category: 'layout',
  attributes: {
        no_title: {
            type: 'boolean',
      default: false,
        },
        max_level: {
            type: 'integer',
      default: 6,
        },
    },
  edit: function(props) {

    return (
    <span>
    <InspectorControls>
      <Panel>
        <PanelBody>
          <PanelRow>
            <ToggleControl
                label={__("Disable heading", 'simpletoc')}
                help={__('Remove "Table of contents" block heading.', 'simpletoc')}
                checked={ props.attributes.no_title }
                onChange={ () => props.setAttributes( {  no_title: ! props.attributes.no_title } ) }
            />
          </PanelRow>
          <PanelRow>
            <SelectControl
                label={__("Maximum Level", 'simpletoc')}
                help={__('Maximum depth of the headings.', 'simpletoc')}
                value= { props.attributes.max_level }
                options={ [
                    { label: __('Including', 'simpletoc') + ' H6 (' + __('Show all', 'simpletoc') + ')' , value: '6' },
                    { label: __('Including', 'simpletoc') + ' H5', value: '5' },
                    { label: __('Including', 'simpletoc') + ' H4', value: '4' },
                    { label: __('Including', 'simpletoc') + ' H3', value: '3' },
                    { label: __('Including', 'simpletoc') + ' H2', value: '2' },
                ] }
                onChange={ ( level ) => props.setAttributes( { max_level: level } ) }
            />
          </PanelRow>
        </PanelBody>
      </Panel>
    </InspectorControls>
    <BlockControls>
      <ToolbarGroup>
        <ToolbarButton
          className="components-icon-button components-toolbar__control"
          label={__('Update table of contents', 'simpletoc')}
          onClick={function() {
            sendfakeAttribute(props)
          }}
          icon="update"
        />
      </ToolbarGroup>
  </BlockControls>
  <p>
    <ServerSideRender block={props.name} attributes={props.attributes} />
  </p>
  </span>
    )
  },
  save: props => {
    return null;
  },
});

The relevant php part:

register_block_type('simpletoc/toc', [
    'editor_script' => 'simpletoc-js',
    'editor_style' => 'simpletoc-editor',
        'attributes' => array(
                'no_title' => array(
                    'type' => 'boolean',
              'default' => false,
                ),
            'max_level' => array(
                    'type' => 'integer',
              'default' => 6,
                ),
            'updated' => array(
              'type' => 'number',
              'default' => 0,
              '_builtIn' => true,
            ),
    ),
    'render_callback' => __NAMESPACE__ . '\render_callback'
   ]);

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

The reason why the block editor uses the default value — for the max_level (“Maximum level”) attribute only — instead of the one parsed from the (block comment delimiter in the) saved post content, is because in the parsed/saved attributes, max_level is invalid.

Why is it invalid: Because the type of the attribute (value) doesn’t match the one defined when the block is registered — max_level: { type: 'integer', ... }.

And why doesn’t it match, is because this part which updates the attribute with a value that is a string:

// "level" here is an input value, and input values are string unless
// explicitly converted to another type (boolean, number, etc.):

onChange={ ( level ) => props.setAttributes( { max_level: level } ) }

Which then, for instance when the “Including H3” is selected as the “Maximum level”, results in "max_level":"3" instead of the correct one, "max_level":3:

  • Bad — max_level is a string: <!-- wp:simpletoc/toc {"max_level":"3"} /-->
  • Good — max_level is an integer: <!-- wp:simpletoc/toc {"max_level":3} /-->

So be sure to respect the attribute type and in your case, you would want to use Number( level ) like so:

props.setAttributes( { max_level: Number( level ) } )

BTW, I could’ve just pointed you to my answer here, but I thought it’d be better if I write an answer specific to this very question. 🙂

Additional Notes

  • The ServerSideRender component uses wp.element.RawHTML() which wraps the output in a div, hence you shouldn’t wrap the ServerSideRender in a p, which (in my case) resulted in an error (in the console) which says, “<div> cannot be a descendant of <p>” ( in fact, the regular HTML <p><div>something</div></p> is indeed invalid 🙂 ). So,
    // Instead of this:
    <p><ServerSideRender .../></p>
    
    // Use a div or Fragment as a wrapper:
    <div><ServerSideRender .../></div>
    <><ServerSideRender .../></>
    
    // Or simply, no wrapper:
    <ServerSideRender .../>
    


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x