Gutenberg is still months away from being rolled into WordPress Core. The code base is being tightened up and more features are being added, but despite active development, one major flaw, in my opinion, still remains with no signs of being resolved.
Now, before I go down this road, the flaw of which I speak isn’t broken code or something that was just missed during the initial scaffolding. It’s a result of the way that Gutenberg, React.js, and its extraction layer work together to get the job done. Specifically, it’s how Gutenberg sets
attributes and renders content to the page and updates
The Standard Block
Let’s walk it through the way that a standard field works. In this example, we’re looking at the default Cover Image block. The field itself allows you to upload a background image and add some text. This block has several attributes to handle alignment, some special features, brightness, etc, but the elements we’ll look at here more closely are the attributes that handle the title, URL, and ID.
When we save our post, the block renders the field HTML to
post_content. The strings (URL and ID) are saved into the block comments as an object accessible after the fact using a library called GitHub – aduth/hpq: Utility to parse and query HTML into an object shape.
But what about our title, which is an array? What happened to that?
The title array was rendered as HTML, formed by the block’s
save function, before it was saved to
post_content. Awesome. It does all the dirty work for us, so there’s no need to manually parse array objects or duplicate additional markup later on. So where’s the downside?
This might be a matter of semantics, but I find it problematic that we’re unable to access the title attribute after the block is rendered and saved to
post_content. This is especially important when using the
render_callback function which has a
save function return a value of
save: () => null in order to access the attribute outside of the block in our plugin or theme’s PHP.
In this example, without a
save function rendering HTML that would then be saved to
post_content there ends up being no saved title attribute or HTML markup in our
post_content—no empty array, no value whatsoever.
When we set our
save function to
null as above, we run with the assumption that we are going to take advantage of the function
render_block( $attributes ) which gives us access to our attributes via the
$attributes variable. The value of which would be equal to ONLY our
url string and our
id integer. We cannot access our title attribute since Gutenberg renders all arrays into HTML (assuming a valid save function) or disregards it entirely upon save, as in our case.
At this point, there are probably two questions. First, what if I don’t want to utilize PHP with my block? Second, why does it matter if our
jsx is handling the render for us, everything it just handled?
These are both valid questions. You certainly don’t need to use PHP to render your block. There are pros and cons for this method which I won’t get into, but it’s not required. And you’re right, if we are just supplying data that needs to be rendered to HTML, there’s no reason we even have to worry about our title attribute array being lost during render.
Enter a practical use case: any dynamic block that utilizes state. A block that simply requires an input of some text or a string or some content doesn’t require any state. You can rely on the block, rendered content, and updates without any fuss. But if you’re perhaps querying posts, for example, selecting posts to display in a drop-down or multi-select block, once you save that output might get rendered to HTML and therefore render to the page. But if you were to refresh or open the post once again, you might find the block to be empty or broken. This is because the expectation of the block is that of the rendered HTML, but the actual output is more likely a drop-down, some HTML or
js that doesn’t render the page. And because our array attribute is empty because the value was rendered to
post_content we can just access the array attribute to repopulate our dropdown and selections.
So, to back up a bit…
Let’s run through the process here.
Our title attribute is an array using the
<RichText /> component here. It generates an array of values, tags, potentially multiline elements, and some other various values, a simple example would just be an array of strings:
When we save that value gets removed and converted into our HTML based on other options supplied to the
<RichText /> component:
Which ends up returning our title attribute to its default state or:
So if we were to try and re-access
this.props.attributes.title the value would be our string until save at which point it would be an empty array. If you rely on this content to render a field that differs from the rendered output, you will find you’re not going to be able to do anything.
Enter a fix
While working on the WDS-Blocks Related Posts block I needed to solve this same problem. The content rendered on the front end by the save function would only match one part of the admin functionality, the display of posts. The multi-select block and specifically
selectedPosts would not have any attributes to reference. This is a dynamic block with multiple working parts. By default, it would seem that Gutenberg intends the rendered content and the admin content to be identical. It does not anticipate more robust functionality.
On refresh, the block would be reset because our
selectedPosts array attribute was emptied to render our HTML. I needed a way to access this data after the fact.
This may be a little convoluted, but the solution works. What I did was add a second attribute string.
That string would be identical to our
selectedPosts array prior to save that had been run through the
JSON.stringify( props.attributes.selectedPosts ) function to render it to string. We can then re-access our array of
selectedPosts by parsing that with
JSON.stringify( props.attributes.selectedPostsJSON )
On page refresh, or revising the post/page, our
selectedPosts attribute may be an empty array, but we still have a set of saved data in our attributes we can utilize to repopulate the state of the block. The
post_content ends up looking like this, in the case of the Cover Image block:
We can see our title array now accessible within the JSON string appended to the block comment object.
If using the PHP
render_callback function, the JSON would be available as an option in the
It would be better to just save attributes to
post_meta out of the box, but this at least gives us a new way to use our information that Gutenberg hasn’t provided us with. It might not be the most efficient, especially if you’re dealing with full post objects, but a string, in general, is far more efficient than an array or object and there are ways to make our JSON data more efficient before it makes its way to
So what do you think?
Can you see this being useful? Are you aware of a better way to save and utilize arrays within Gutenberg? I’d love to hear about it.