Multiple RichText components cause issues in WordPress
While building Gutenberg Block plugins for WordPress I’ve run into a problem with the inbuilt RichText component where the blocks don’t validate when reloaded. It’s caused by using RichText in multiple places within the block while sticking to the format of WordPress’ example code.
Digging deeper I found that the block’s save function would write the data out correctly, but when I reloaded the page in the editor, it seemed to no longer know the right data and confuse the two fields.
This article will look at the cause of that and the fairly straightforward fix.
The problem
Most tutorials I found, as well as the RichText documentation (At the time of writing), present you with example attribute definition for RichText that looks like this.
// Attributes definition
cardBackContent: {
type: 'string',
source: 'html',
selector: 'p',
},
Which would be used in an edit and save function like this.
// Edit JSX
<RichText
tagName = "p"
value = {attributes.cardBackContent}
onChange = { (value) => setAttributes({cardBackContent: value}) }
placeholder = "Enter text..."
/>
// Save JSX
<RichText
tagName = "p"
value = {attributes.cardBackContent}
/>
If you happen to be making a block that needs two RichText components in it. You will end up with something like below—And this is where the code stops working.
You’ll find that your block seems to work fine while editing and viewing on the front end, but as soon as you open the file again in the editor, it doesn’t validate and appears broken (see console screenshot below).
This is roughly what your code might look like for reference.
// Attributes definition
cardBackContent: {
type: 'string',
source: 'html',
selector: 'p',
},
cardFrontContent: {
type: 'string',
source: 'html',
selector: 'p',
},
// Edit JSX
<div class="card-back">
<RichText
tagName = "p"
value = {attributes.cardBackContent}
onChange = { (value) => setAttributes({cardBackContent: value}) }
placeholder = "Enter text..."
/>
</div>
<div class="card-front">
<RichText
tagName = "p"
value = {attributes.cardFrontContent}
onChange = { (value) => setAttributes({cardFrontContent: value}) }
placeholder = "Enter text..."
/>
</div>
// Save JSX
<div class="card-back">
<RichText
tagName = "p"
value = {attributes.cardBackContent}
/>
</div>
<div class="card-front">
<RichText
tagName = "p"
value = {attributes.cardFrontContent}
/>
</div>
This is an example error seen on opening the file again in the editor.
The error shown above from the console describes that the content produced by the save function (when reloading the page in the editor), doesn’t match what was saved last time the file was edited.
This is a validation check by WordPress to prevent the user interacting with blocks that have broken during an upgrade or something else.
The cause
What causes this are those lines in our attributes definition: “source: html” and “selector: p”. These two lines tell WordPress how to save and retrieve the data.
Specifically, those tell WordPress the source of the data will be the HTML that’s output. So upon reload, WordPress should select the p tag and grab the content straight out of it.
The problem with this, is there are two p tags. This doesn’t create an issue when WordPress is writing the data, but when the file is opened again in the editor, each RichText component looks for a p tag and takes the first one it finds. This is why you might have noticed in your file that both fields show the same content.
The solution
To solve this, there are two solutions.
Solution 1
Remove the lines source: html and selector: p from the attributes definition.
These lines are an optional way to tell WordPress that you will manage how it saves data. But you don’t have to. If you remove those lines WordPress will save the data as comments in the block’s code and will handle multiple RichText components properly on it’s own.
Solution 2
If you want to manage the data yourself, you need to make sure that the selector value you define in the attributes file is guaranteed to be unique.
Ordinarily, you might think about using an id name on the paragraph block here, however, remember this is a block’s definition. The user might choose to add multiple of these blocks and then you’ll end up with multiple, identical id’s on the page—Which will create other issues (as id’s must be unique to a page).
Instead, use a class name.
// Attributes
cardFrontContent: {
type: 'string',
source: 'html',
selector: '.front-content',
default: defaultAttributes.frontContent
},
// Edit.jsx
<RichText
tagName = "p"
class = "front-content"
value = {attributes.cardFrontContent}
onChange = { (value) => setAttributes({cardFrontContent: value}) }
placeholder = "Enter text..."
/>
Save.jsx
<RichText
tagName = "p"
class = "front-content"
value = {attributes.cardFrontContent}
/>
Note that the RichText components only search within the content of the block their in, so the selector doesn’t need to be unique to the whole page, just the block.
That’s it!
Remember, there were two options above. The easier one only involves deleting a couple of lines, and unless you have a reason to work the second way, you can just do that.
Why would you go for the more complicated option when WordPress can manage it for you?
If you’re really worried about download size of each pages html, it might be advantageous to specify source: html, to minimise the extra comments WordPress needs to add in. However, as an optimisation, that’s something you can always improve later if you still find it necessary.
Keep things simple unless you really need to.
Thanks…
I also dissect and speculate on design and development.
Digging into subtle details and implications, and exploring broad perspectives and potential paradigm shifts.
Check out my conceptual articles on Substack or find my latest below.
You can also find me on Threads, Bluesky, Mastodon, or X for more diverse posts about ongoing projects.
Leave a Reply