The WordPress Block Editor (aka Gutenberg), despite all the bumps along the way, has completely changed how we write pages and posts. Instead of a text document, we now have access to an interactive page builder of sorts while editing content, at least on the admin side. But, what if you want to perform frontend editing?
Well, depending on your coding preferences, you could build a frontend form to update post meta, or implement some custom jQuery with AJAX, or use a page builder with frontend editing capabilities. If, on the other hand, you’ve been experimenting with your own blocks and are looking for a way to update block content and attributes from outside the confines of the post editor, then buckle up, and keep reading.
Note: The example snippets in this post are modified from @ravewebdev/initiative-tracker, my Initiative Tracker block plugin for table-top role-playing games, which was built with @WebDevStudios/create-block, the WDS block scaffolding CLI tool.
1. Block Setup
First off, we’re going to cover some necessary block configurations to consider when diving into frontend block editing:
- Unique block identifiers
- Frontend rendering
1.1. Unique Block Identifiers
When you work with blocks in the admin, WordPress handles differentiating blocks automatically. That means, when you add multiple instances of the same block, you don’t have to worry that editing one will somehow change anything about another. They’re unique and self-contained.
Unfortunately, when messing around with frontend editing, we don’t have access to the built-in block attributes for uniquely identifying and reliably targeting a block, so we’ll need to roll our own:
First, we’ll add a new attribute called
id with the type
string to the block configuration. In our block’s
edit function, we’ll set our new
id attribute to some unique string.
You have several options here, but in this example, we’re using the built-in
clientId property, an alphanumeric string such as
1bb09bce-77a1-46ee-bff5-ad6e41b13f3e that uniquely identifies a block.
The actual string itself is less important than ensuring it’s both unique and only set once, at the time the block is created. This ensures you can reliably and accurately target the correct block and prevents the value from being changed or overwritten later on.
Without the latter, the
id attribute could change on every load of the edit screen and/or every render of the block, which makes WordPress think you’ve intentionally edited the post content. This triggers the dreaded “Are you sure you want to leave this page?” popup; and, the next time the post is edited, potentially displays the autosave warning, “The backup of this post in your browser is different from the version below.” Both of these instances, you are probably very familiar with and often find them annoying if you do a lot of development work in the block editor.
So, let’s try to avoid that, shall we? That’s where
useEffect() comes in.
Our block’s edit property is a function component, so we’ll utilize the Effect Hook to perform a side effect, which will set our ID attribute. By passing an empty array,
, as the second argument, we specify that this effect should only occur once, on mount, i.e., immediately following the initial render. For more information, check out the note at the bottom of the Optimizing Performance section of the Effect Hook doc.
But, we’re not done, yet! While the Effect Hook helps us avoid extraneous attribute resetting on every render, it will still run every time the edit screen is loaded. To ensure we only run it once, right when the block is created, we’ll perform a quick check via
if statement to see if the ID attribute has already been set.
1.2. Frontend Rendering
Now, we’re getting into the actual frontend display of our block, but before we get into the fun (tricky? mind-melting?) stuff, we need to ensure we’re building a dynamic block.
Luckily, that’s as simple as returning
null in our block’s save function (as shown in the previous step), which instructs WordPress to save the block’s attributes without any markup. Without this step, frontend edits to our block could break it, causing a validation message like, “This block appears to have been modified externally,” the next time we try to edit our post in the admin.
Of course, returning
null means our block no longer appears on the frontend at all. So, we need to handle its rendering another way… with PHP! To do this, we’ll provide a
render_callback function when calling
register_block_type() for our block, like below:
This callback function receives an array of the block’s attributes and returns a string of HTML representing our block. For the most part, the HTML output here should mirror the JSX we’d use to display the block with React.
For the purposes of frontend editing, there are two important data attributes we need to output in our block wrapper:
id– The block ID attribute we created above.
post_id– The current post (or page or CPT) ID.
Note: It’s a good idea to pass any other block attributes you’ll need as additional data attributes to help with rendering your block in React later on.
2. Frontend React
- Frontend React component
- Static block replacement
2.1. Frontend React Component
For the most part, we’ll want to rely on any components already created for rendering our block in the admin to render it again on the frontend; however, creating a new frontend-only component is useful as an intermediary. So, that’s what we’ll be tackling now.
Here we’re setting up the basic function component that we’ll use as a wrapper to render our block on the frontend. First, we destructure our
props to get our
dataAttributes, which will be an object created from the data attributes we defined in step 1.2. Given how we’ll be rendering our frontend component, the initial props we receive above won’t update when we change things in our block.
Therefore, if we want to track changes between renders, we’ll need to use the State Hook,
useState(), within our new frontend component. In this instance, we’ll be keeping track of our block’s attributes with a single state variable,
attributes, as an object; although you’re welcome to refactor your code to use multiple individual state variables instead.
Similar to earlier when we set the block ID in the admin in step 1.1., we’ll again take advantage of
useEffect(), passing an empty array,
, as the second argument to apply this effect only once. Here, we’ll override our new state variable,
attributes, with the values we were passed via
This ensures the initial values of our data attributes are reflected in our block’s state. Later, when we actually get into the process of frontend editing, we’ll update this state variable to track any changes to our block attributes (e.g., with an input’s
At the bottom of our new frontend component, we need to call our pre-existing components (the ones we used in the admin to display our block). Here, we’re wrapping everything in a React Fragment (
<>) in case we have multiple components to display, but if you only need to call one component, you can remove the wrapping fragment to simplify the return.
2.2. Static Block Replacement
wp-api-fetch as a dependency for this new file; this will allow us to use
apiFetch() to make our frontend updates later on.
We’ll define the target class for our block, use it to retrieve all instances of our target block with
document.querySelectorAll(), then iterate over each instance. For each instance, we’ll extract the block attributes from the data attributes we defined in step 1.2. For some of these data attributes, we may want to parse the values to get the necessary data format. For example, we’ve used
parseInt() to retrieve the integer value of the passed post ID.
element: The element to be rendered—our frontend component—to which we pass the variable we just created,
attributes, as the
target: The DOM container node we want to update—the
trackerelement—which represents the current instance of our custom block. Note: the element passed will replace all contents of the target container but not the container node itself.
Hooray! Our block is now being displayed dynamically with React on the frontend.
We’ve laid the groundwork in preparation for frontend editing with our block. We covered properly identifying a block instance, dynamic block rendering with PHP, then replacing our plain HTML block with a frontend React component. Let’s take a breather before diving into the WordPress Rest API and request fetching in Part 2.