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 post_content
.
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.
So we can see above that the URL and image ID is added to the comment and therefore we can access those again later using JavaScript of the PHP render_callback
function.
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 null
or 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 $attributes
variable.
👍🏼
It would be better to just save attributes to post_content
or 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 post_content
.
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.