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
At this point, our (currently static) block should be displaying on the frontend. Phew! Next, we’re going to tackle re-rendering our block with JavaScript and JSX by implementing the following steps:
- 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 dataAttributes
.
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 onChange
event).
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
Now that we’ve gotten the basics of our frontend component set up, we need to replace the static HTML block we created in PHP. The code in this section will need to be placed in a new JavaScript file enqueued on the frontend of the site. And, as a bit of foreshadowing, make sure to add 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.
Then we call wp.element.render()
, an abstraction of ReactDOM.render()
, which takes two parameters:
element
: The element to be rendered—our frontend component—to which we pass the variable we just created,attributes
, as thedataAttributes
property value.target
: The DOM container node we want to update—thetracker
element—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.
Next Steps
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.
Please, this is awesome.
– Would you mind the Code to GitHub?
Thanks…
Hi, Tobi. Thanks for commenting. Rebekah linked to her Github at the beginning of this blog post, so that you can access her code. But, here is the link to her Github, as well. Thank you. https://github.com/ravewebdev/initiative-tracker