Say Goodbye to RichText Multiline: How to Use InnerBlocks Instead

I received a support request saying one of my plugins was causing a console error. The console error explained that multiline in the RichText component was being deprecated in WordPress 6.3. The recommendation was to convert the RichText component into InnerBlocks instead.

Console Error Saying the RichText Multiline Is Being Deprecated
Console Error Saying the RichText Multiline Is Being Deprecated

Armed with the experience of an InnerBlocks noob, I decided to convert my block over. It wasn’t easy. So I thought I’d share what I’ve learned.

In this tutorial, I’ll explain the why behind the deprecation, how to use InnerBlocks instead, and how to convert your RichText content to InnerBlocks.

Why the deprecation?

According to the PR, having multiline in the RichText component introduces complexities that need not exist.

Handling multi line content in RichText is quite complex.

1. There’s 10 out of 28 rich text manipulation functions that are just for multi line handling.

2. There’s significant amount of added complexity in the create and toTree functions to convert between DOM/HTML and the rich text value.

Deprecation would mean that after a WP release we can consider removing this code, which makes rich text easier to maintain and makes refactors easier to do.

ellatrix
GitHub PR

Multiline is no longer considered a best practice, so it is recommended to use InnerBlocks instead. While harder to implement, InnerBlocks give the greatest flexibility going forward.

What are InnerBlocks?

InnerBlocks allow you to nest other blocks within a parent block. It enables you to create more complex blocks by combining multiple smaller blocks inside the main one.

With InnerBlocks, you can define a template of supported blocks and allow for more complex internals with regards to the output.

InnerBlocks Example in the Highlight and Share Block
InnerBlocks Example in the Highlight and Share Block

Unlike with the RichText component, there are no attributes to save, and you can only have one instance of InnerBlocks per block.

Let’s talk about the RichText block

We need something to convert to InnerBlocks for this tutorial, so I’ve created a very simple block that uses the @wordpress/scripts library. It’s a simple block with just a RichText component in the block editor. But I’ve enabled it so we can enter in and save our RichText data, which will be converted over to InnerBlocks.

The folder structure

We’ll end up with a simple folder structure, as I will do much of the functionality in the main PHP file for simplification.

.
└── dlxrichtext/
    ├── dlxrichtext.php
    ├── package.json
    ├── build
    └── src/
        ├── index.js
        └── blocks/
            └── dlxrichtext/
                ├── block.js
                ├── edit.js
                └── block.jsonCode language: AsciiDoc (asciidoc)

The bulk of the plugin is in dlxrichtext.php and also in the src folder, which is where the block code lives.

The package.json file

I’m using the @wordpress/scripts NPM package for the block. Right now the package.json file is very bare bones and only has one dependency.

{
	"name": "dlxrichtext",
	"version": "1.0.0",
	"description": "Convert RichText to InnerBlocks",
	"main": "index.js",
	"scripts": {
		"start": "wp-scripts start --env mode=development",
		"build": "wp-scripts build --env mode=production"
	},
	"repository": {
		"type": "git",
		"url": "git+https://github.com/DLXPlugins/DLXRichText.git"
	},
	"author": "Ronald Huereca",
	"license": "GPL-3.0-or-later",
	"bugs": {
		"url": "https://github.com/DLXPlugins/DLXRichText/issues"
	},
	"homepage": "https://github.com/DLXPlugins/DLXRichText#readme",
	"devDependencies": {
		"@wordpress/scripts": "^26.0.0"
	}
}
Code language: JSON / JSON with Comments (json)

In terminal, you’d run npm install, which will install @wordpress/scripts. You’d then run npm run start to build the scripts and styles into a build folder.

Let’s move on to the PHP side of things and initialize the plugin.

The plugin header

In the main plugin file, dlxrichtext.php, I set up the plugin headers and namespace.

<?php
/**
 * Plugin Name:       DLX RichText
 * Plugin URI:        https://dlxplugins.com/plugins/
 * Description:       Demonstrates how to convert RichText to InnerBlocks
 * Version:           1.0.0
 * Requires at least: 5.9
 * Requires PHP:      7.2
 * Author:            DLX Plugins
 * Author URI:        https://dlxplugins.com
 * License:           GPL v2 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       dlxrichtext
 *
 * @package DLXRichText
 */

namespace DLXPlugins\DLXRichText;Code language: PHP (php)

Up next, I register the block, referencing the block.json file I created. We’ll go over the block.json file in a bit.

/**
 * Register the block and set server-side remder.
 */
function register_block() {
	register_block_type(
		plugin_dir_path( __FILE__ ) . 'build/blocks/dlxrichtext/block.json',
		array(
			'render_callback' => __NAMESPACE__ . '\frontend_block_output',
		)
	);
}
add_action( 'init', __NAMESPACE__ . '\register_block' );Code language: PHP (php)

For the block registration, I am using server-side rendering on the frontend with a callback function frontend_block_output. Here’s how I process the frontend.

/**
 * Output block to frontend.
 *
 * @param array $attributes Block attributes.
 */
function frontend_block_output( $attributes ) {
	$richtext_content = $attributes['textContent'] ?? '';
	return sprintf(
		'<div class="dlxrichtext-frontend">%s</div>',
		wp_kses_post( $richtext_content )
	);
}Code language: PHP (php)

And lastly, I enqueue my JavaScript file:

/**
 * Enqueue block editor assets.
 */
function enqueue_block_editor_assets() {
	wp_register_script(
		'dlx-richtext-editor-script',
		plugins_url( 'build/index.js', __FILE__ ),
		array(),
		'1.0.0',
		true
	);
}
add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\enqueue_block_editor_assets' );Code language: PHP (php)

Initializing the block

As a reminder, here’s the folder structure for the block:

.
└── src/
    ├── index.js
    └── blocks/
        └── dlxrichtext/
            ├── block.js
            ├── edit.js
            └── block.jsonCode language: AsciiDoc (asciidoc)

The main logic starts with block.json, which defines the attributes and general settings for the block.

{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"title": "DLX RichText",
	"apiVersion": 2,
	"name": "dlxplugins/dlxrichtext",
	"category": "common",
	"icon": "editor-spellcheck",
	"description": "A demonstration of a RichText component.",
	"keywords": ["rich", "text", "innerblocks", "inner"],
	"version": "1.0.0",
	"textdomain": "dlxrichtext",
	"attributes": {
		"textContent": {
			"type": "string",
			"default": ""
		}
	},
	"example": {
		"attributes": {
			"textContent": "<p>This is a sample RichText block.</p>"
		}
	},
	"editorScript": "dlx-richtext-editor-script"
}Code language: JSON / JSON with Comments (json)

Within block.json, I define one attribute named textContent. This attribute will hold the value of the RichText component.

I also use an editorScript handle of dlx-richtext-editor-script, which is referenced in PHP above for the script handle when enqueueing.

Let’s move onto block.js.

import { registerBlockType } from '@wordpress/blocks';
import metadata from './block.json';
import Edit from './edit';

registerBlockType( metadata, {
	edit: Edit,
	save() {
		return null;
	},
} );Code language: JavaScript (javascript)

In block.js, I gather the block.json file and use registerBlockType to initialize the block in JavaScript.

Note the save property. I return null because I am rendering the block frontend on the server.

For the edit property, I’m pulling in edit.js. Since there is only one attribute to keep track of, the file is pretty light.

import { __ } from '@wordpress/i18n';

import {
	RichText,
	useBlockProps,
} from '@wordpress/block-editor';

const DLXRichText = ( props ) => {

	// Shortcuts.
	const { attributes, setAttributes } = props;

	const {
		textContent,
	} = attributes;

	const block = (
		<div className="dlx-text-wrapper">
			<RichText
				tagName="div"
				multiline="p"
				placeholder={ __( 'Enter some text', 'quotes-dlx' ) }
				value={ textContent }
				className="dlx-text-content"
				allowedFormats={ [ 'core/bold', 'core/italic', 'core/link' ] }
				onChange={ ( value ) => {
					setAttributes( { textContent: value } );
				} }
			/>
		</div>
	);

	const blockProps = useBlockProps( {
		className: "dlx-rich-text",
	} );

	return (
		<>
			<div { ...blockProps }>{ block }</div>
		</>
	);
};

export default DLXRichText;
Code language: JavaScript (javascript)

Scanning the code, you’ll see I’m using the RichText component with the attribute textContent. I’m also using multiline, which is a property that is being deprecated in WordPress 6.3.

<RichText
	tagName="div"
	multiline="p"
	placeholder={ __( 'Enter some text', 'quotes-dlx' ) }
	value={ textContent }
	className="dlx-text-content"
	allowedFormats={ [ 'core/bold', 'core/italic', 'core/link' ] }
	onChange={ ( value ) => {
		setAttributes( { textContent: value } );
	} }
/>
Code language: JavaScript (javascript)

Lastly, we’ll look at our entry point:

.
└── src/
    └── index.jsCode language: AsciiDoc (asciidoc)

It’s just a basic import.

import DLXRichText from "./blocks/dlxrichtext/block";Code language: JavaScript (javascript)

The end result

We end up with a block that has a single RichText component.

RichText Block in the Block Editor
RichText Block in the Block Editor

Converting to InnerBlocks

With the deprecation warning, you’re advised to switch from RichText to InnerBlocks. The InnerBlocks documentation is pretty high-level and doesn’t show how to convert from RichText to InnerBlocks. So let’s start off on the right foot, and instead of using InnerBlocks, we’ll use useInnerBlocksProps.

Using useInnerBlocksProps

It is now recommended to use useInnerBlockProps to output your InnerBlocks. Let’s start by importing the dependency in our edit.js file.

.
└── src/
    ├── index.js
    └── blocks/
        └── dlxrichtext/
            ├── block.js
            ├── edit.js
            └── block.json
Code language: AsciiDoc (asciidoc)
import {
	RichText,
	useBlockProps,
	useInnerBlocksProps
} from '@wordpress/block-editor';Code language: JavaScript (javascript)

We’ll also need a variable that will act as a reference to the InnerBlocks, so we’ll define useRef as a dependency as well.

import { useRef } from '@wordpress/element';Code language: JavaScript (javascript)

Within the block, I’ll assign a variable to use useRef.

const DLXRichText = ( props ) => {

	const innerBlocksRef = useRef( null );

	// Shortcuts.
	const { attributes, setAttributes } = props;

	/* .. more code ..*/
};
Code language: JavaScript (javascript)

Next, we’ll define a constant that holds the hook value of useInnerBlockProps.

const DLXRichText = ( props ) => {

	const innerBlocksRef = useRef( null );

	const innerBlockProps = useInnerBlocksProps(
		{
			className: 'has-click-to-share-text has-click-to-share__share-text',
			ref: innerBlocksRef,
		},
		{
			allowedBlocks: [ 'core/paragraph' ],
			template: [ [ 'core/paragraph', { placeholder: '' } ] ],
		}
	);

	/* more code */
};
Code language: JavaScript (javascript)

The first object being passed to useInnerBlockProps are the class names being used, and you can also add a reference, which is what I’ve added.

The second object contains allowedBlocks, which is an array of blocks supported for InnerBlocks.

The template array contains what should show up when there are no InnerBlocks present.

Since my RichText component is multiline and splits the lines with paragraphs, I can just use a simple paragraphs template to lay out my InnerBlocks.

Now I can output my InnerBlocks next to my RichText component.

const block = (
	<div className="dlx-text-wrapper">
		<RichText
			tagName="div"
			multiline="p"
			placeholder={ __( 'Enter some text', 'quotes-dlx' ) }
			value={ textContent }
			className="dlx-text-content"
			allowedFormats={ [ 'core/bold', 'core/italic', 'core/link' ] }
			onChange={ ( value ) => {
				setAttributes( { textContent: value } );
			} }
		/>
		<div { ...innerBlockProps } />
	</div>
);
Code language: JavaScript (javascript)

The end result is InnerBlocks beneath my RichText component.

RichText Component and InnerBlocks
RichText Component and InnerBlocks

Saving the InnerBlocks

Since there are no attributes for InnerBlocks, how do you save them? You have to go back into the main block file block.js and modify the save property for InnerBlocks.

import { registerBlockType } from '@wordpress/blocks';
import { InnerBlocks } from '@wordpress/block-editor';
import metadata from './block.json';
import Edit from './edit';

registerBlockType( metadata, {
	edit: Edit,
	save() {
		return <InnerBlocks.Content />;
	},
} );

Code language: JavaScript (javascript)

You’ll need to import InnerBlocks and also return the content via the save function. If you’re using server-side on the frontend, don’t worry, your existing attributes are safe.

Outputting the InnerBlocks

On the frontend, let’s head back to dlxrichtext.php and modify the block output.

InnerBlocks are passed via a second parameter called $content to the render_callback and can be outputted as HTML.

/**
 * Output block to frontend.
 *
 * @param array  $attributes Block attributes.
 * @param string $content    InnerBlocks content.
 */
function frontend_block_output( $attributes, $content ) {
	return sprintf(
		'<div class="dlxrichtext-frontend">%s</div>',
		wp_kses_post( $content )
	);
}
Code language: PHP (php)

As I am no longer using RichText, I can just output the $content variable directly.

Migrating from RichText to InnerBlocks

So far with InnerBlocks we’ve achieved the following:

  1. Outputting the InnerBlocks in our block
  2. Saving the InnerBlocks
  3. Outputting the InnerBlocks on the frontend

Now we need to migrate what’s in the textContent attribute over to InnerBlocks.

First, we need to import a few more dependencies in edit.js.

import { rawHandler } from '@wordpress/blocks';Code language: JavaScript (javascript)

The rawHandler dependency will do the bulk of the conversion.

Next, we need the data store for the block editor so we can replace the InnerBlocks with the converted items.

import {
	RichText,
	useBlockProps,
	useInnerBlocksProps,
	store
} from '@wordpress/block-editor';
Code language: JavaScript (javascript)

We’ll need useDispatch to communicate with the WordPress data store.

import { useDispatch } from '@wordpress/data';Code language: JavaScript (javascript)

We’ll need to add useEffect as an import so we can migrate the RichText content over when the block loads.

import { useRef, useEffect } from '@wordpress/element';Code language: JavaScript (javascript)

We need to account for the block’s clientId, which is passed automatically to a block via props.

const { attributes, setAttributes, clientId } = props;Code language: JavaScript (javascript)

We’ll also need to use replaceInnerBlocks from the WordPress store.

const { replaceInnerBlocks } = useDispatch( store );Code language: JavaScript (javascript)

Finally, we’ll use useEffect to migrate the RichText component over.

/**
 * Migrate RichText to InnerBlocks.
 */
useEffect( () => {
	// Port shareText attribute to use innerBlocks instead.
	if ( textContent !== '' && null !== innerBlocksRef.current ) {
		// Convert text over to blocks.
		const richTextConvertedToBlocks = rawHandler( { HTML: textContent } );
		replaceInnerBlocks( clientId, richTextConvertedToBlocks );
		setAttributes( { textContent: '' } );
	}
}, [ innerBlocksRef ] );Code language: JavaScript (javascript)

The above code is doing quite a bit, and I’ll do my best to explain it.

  1. If the textContent attribute isn’t empty and the InnerBlocks have finished rendering, I use rawHandler to convert my RichText data over to blocks.
  2. Use replaceInnerblocks and pass it the clientId of the block and the converted blocks.
  3. Set textContent attribute to empty so it isn’t converted again.

Any items I had in my RichText component will now be converted when the block loads.

RichText Conversion on Block Load
RichText Conversion on Block Load

You can now safely remove the RichText component.

Conclusion

In this tutorial, I demonstrated how to create a block using the RichText component. I then demonstrated how to convert the RichText data into InnerBlocks.

If you have any questions, comments are open and welcome.

Ronald Huereca
By: Ronald Huereca
Published On: on March 23, 2023

Ronald Huereca founded DLX Plugins in 2022 with the goal of providing deluxe plugins available for download. Find out more about DLX Plugins, check out some tutorials, and check out our plugins.

4 thoughts on “Say Goodbye to RichText Multiline: How to Use InnerBlocks Instead”

  1. What I can’t figure out is how to fix a block where I’m using multiple separate RichText components. I can’t use multiple InnerBlocks areas. So how do I solve it?

  2. @Markus Finell I’ve solved a variation of this by using child blocks, which can have their own inner blocks.

Leave Your Valuable Feedback

Your email address will not be published. Required fields are marked *

Shopping Cart
  • Your cart is empty.
Scroll to Top