When the first beta of Gutenberg was made available for testing, one of the most controversial decisions was that the default storage for block attributes is having the HTML saved in post content. Block attributes—the settings on blocks—can also be stored in post meta or other locations, but the system most core blocks use is to save the data as HTML (as the end user will view it) or to serialize it to comments.
For example, to slightly oversimplify how the core paragraph block works: in post content, you have some HTML like this:
As another example, the source of the image in an image block is stored in the src attribute of the image tag.
Hydration And Server-Side Rendering
In my post on Gutenberg and Headless for Pantheon, I talked about reusing components between Gutenberg and other apps and gave some basic examples of how to re-use components in the front-end and editor. I didn't get too far into how to do so. I gave one example of saving an empty HTML container in the block and then in the front-end, mounting the same component on it.
This follows how we generally use React and ReactDOM for apps. Compare this fairly standard way of mounting a React app:
Using ReactDOM.render() misses a huge opportunity. In the block editor, WordPress parses the attributes back to an object and hydrates its state from the saved HTML. This got me thinking: I know how to use ReactDOM.hydrate() to hydrate a server-side rendered React app running in node, so why can't I use ReactDOM.hydrate() to hydrate my blocks in the front-end?
It turned out, this works pretty well. In this article, I'm going to show how to build a block that adds a form to the page—something I know about. The form will become interactive in the front-end.
The Gutenberg team recently released the @wordpress/scripts package to simplify building, testing and linting blocks. It's really cool and very simple. I've spent a lot of time setting this all up manually, so I love this new, three-step process:
First, add the package to your project:
npm install @wordpress/scripts --save-dev
Then add scripts to package.json:
"build": "wp-scripts build",
"check-engines": "wp-scripts check-engines",
"check-licenses": "wp-scripts check-licenses --production",
"lint:css": "wp-scripts lint-style '**/*.css'",
"lint:js": "wp-scripts lint-js .",
"lint:pkg-json": "wp-scripts lint-pkg-json .", "start": "wp-scripts start",
"test:e2e": "wp-scripts test-e2e --config e2e/jest.config.js",
"test:unit": "wp-scripts test-unit-js",
"env:start": "bash start.sh"
Last, create a block in src/index.js (we will get more into that next).
You can see a more complete list of commands in the README, but this gives us the ability to compile, with a hot module replacement by running `yarn start` and an optimized build by running `yarn build`.
This package requires no config to enable testing, linting and more. It is extensible, which we will get into later in this post, but first, let's make a block!
Composing Blocks From Components
Ok, now we need a block. I'm assuming in this article that you know how to build blocks and are familiar with React. If not, then I recommend taking a look at some more basics articles first. For a walk through of a basic block, with non ES6, I recommend this post by Amanda Giles. You should also check out the official tutorial on block creation. Zac Gordon’s complete course on Gutenberg development, which I also recommend.
I will walk through the block design highlighting what's unusual and unique on this.
Before we can flesh out an API for our components, we need to discuss what shared state they will pass around. This way we can design the components around displaying and updating those values. In Gutenberg, state is described with block attributes, so we will start there:
The block we are creating adds an input field to the post, so the first attribute is its default value. That attribute is stored in the value attribute of the HTML input that we will save for the block. The other attribute is the block ID. That's a unique ID for the block that will also get appended to the input, this is important for the front-end.
We have one piece of state to display—the default value. We will need a component to edit that attribute and a component to display it—in the block editor or front-end.
Let's start with the component to display the form, let's call it Form:
This is a pure component; it has no side effects. It is also a controlled component, its state is managed by its parent. If you used this component alone, changes to the input would not change the value. This is good, it's decoupled from the state management system. Let's wire it up to WordPress in the block editor.
In src/index.js, create a new block, I trust you know how to do that, using the attributes I showed earlier. Here is a skeleton block to start from:
That's a placeholder. First thing we need to do is put the Form component in the edit callback. It's going to need three props—defaultValue, onChange and id. The defaultValue and id we get from Gutenberg, via our attributes:
We need a change handler for both of these, one to pass down to our component and one to capture a unique id for the block:
Now, we have everything we need to display the form in the edit callback:
Because this UI is a form field, it made sense to use that form field to update its own default value, so I passed my change handler there. I still wanted to have controls in the block sidebar, so I think it makes sense to add that in using inspector controls. Here is the full editor:
Saving Isomorphic Blocks
Now that we have the edit interface for the block, we need to add the save callback. The goal here is to put HTML on the page that is valid and has the saved state. We don't want to assume that the front-end will become interactive via React, but our goal is to make that possible.
To satisfy our goal of being totally isomorphic - lets use the same component as we used to preview it:
That component is going to be used again in the front-end. That's the point. One component, use as much as possible. This saves the default value attribute, class name—how WordPress identifies the block type—and unique Id to the DOM.
You can think of the WordPress scripts' webpack configuration as something that can be overridden as needed. In this case, we need to change the setting for "entry". According to WordPress' docs, we do this by creating a webpack.config.js file and merging the default config from WordPress with our own. Here is what that looks like:
This will compile /src/front.js to /build/front.js. That file will need to get loaded or inlined wherever the block is loaded.
The block's HTML is already on the page, as is its state. Once the page is loaded, we will pick that block's state off of the DOM and then rehdyrate it. Just to prove it works, my example is going to be to add an extra element to the DOM to display changes to the value in real time.
In this demo, the content will move around a little bit when the app is hydrated. That is actually what this approach, if done right avoids. If everything you need for your interactive app is already on the DOM, there is no flash of empty space as you wait for the app to load, it's just there and the end-user doesn’t really notice that it changed from plain HTML to a React app, since the end result is the same HTML on the page.
In our case, WordPress already took care of the render. We just need to do step two of the process- hydrating.
WordPress identifies block type by a class name, so we can query for elements that have that class to see if any of our blocks are on the page:
If we have matching elements, we can loop through each one in the collection, find its state by reading the DOM attributes and use those to hydrate the app.
I am referring to this as "the app" as each instance is a mini-app. Because Gutenberg is not providing state management, we will need to provide our own. This will be a wrapper around our form component, which does not manage state, to provide state management:
That's a bit simplified from how the full example works. Check it out on Github:
For a simple block like this, I am OK with setting state from the DOM this way. But there are some reasons that might not scale. For a more complex block with tons of attributes, the DOM reads might become slow. Also, the data is static. In some cases it might make more sense to supply state from an API response. That way there is less DOM manipulation and the content of the block can be updated after the post the block is in is updated.
The other issue I see with my approach of reading the attributes this way is its reverse-engineering Gutenberg. Gutenberg supplies a parser we could use. That's more complexity and out of scope of this article, but might be a better option than expanding on the naive implementation I have here.
How blocks are displayed in the front-end of WordPress sites or decoupled clients, is a question that I've been asked a lot at WordCamps. There is no right answer, but I like to think HTML is the answer, most of the time. It took me awhile to get to this solution. What do you think about it?Topics: Development, WordPress