Skip to content

RFC: React Components to Gutenberg Blocks #1522

@theodesp

Description

@theodesp

Intro

This document provides a detailed proposal for converting existing React components into Gutenberg blocks. Gutenberg blocks are the fundamental building blocks used in the new WordPress editor, also known as Gutenberg. The goal of this RFC is to streamline the process of integrating React components into WordPress using Gutenberg, which will enhance the development process and make it more efficient and pleasant for the developer.

Motivation

WordPress powers a significant portion of the web, and React is a popular library for building user interfaces. Converting React components into Gutenberg blocks will enable developers to utilize the power of React in WordPress development. This approach will also make it easier to keep the code DRY (Don't Repeat Yourself) and maintain a consistent UI across different parts of a WordPress site.

For example, in many cases a developer has created many React Component that would like to use in the Gutenberg Editor. Currently there is no standard way to do this. As a matter of fact, most development of Gutenberg Blocks happen first and then are converted to React and not vice verca. This creates a lot of friction and slowing adoption of Headless WordPress initiative.

Detailed Design

The proposed solution involves creating a wrapper component that would serve as a bridge between React and Gutenberg. This wrapper component would be responsible for rendering the React component inside a Gutenberg block.

Here is an example of what the code might look like:

// MyFirstBlock.js
function MyFirstBlock({ style, className, attributes, children, ...props }) {
	const styles = {
		...style,
		backgroundColor: attributes.bg_color,
		color: attributes.text_color,
	};
	return (
		<div
			{...props}
			style={styles}
			className={className}
			dangerouslySetInnerHTML={{ __html: attributes.message }}
		/>
	);
}
// block.json
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 2,
	"name": "create-block/my-first-block",
	"version": "0.1.0",
	"title": "My First Block",
	"category": "widgets",
	"icon": "smiley",
	"description": "My block",
	"supports": {
		"html": false
	},
	"attributes": {
		"message": {
		   "type": "string",
		   "source": "text",
                    "selector": "div",
                    "default": ""
		},
		"bg_color": { "type": "string", "default": "#000000" },
        "text_color": { "type": "string", "default": "#ffffff" }
	},
	"textdomain": "my-first-block",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css"
}
// index.js
import './style.scss';
// import block.json
import metadata from './block.json';

// import Pure React Component
import MyFirstBlock from './MyFirstBlock';

// Register React Component in Gutenberg
import { registerFaustBlock } from '@faustwp/block-editor-utils';

registerFaustBlock(MyFirstBlock, {blockJson: metadata})

In this example,MyFirstBlock is a React component that we want to convert into a Gutenberg block.

There is a new package that exposes a helper for facilitating this conversion.

registerFaustBlock takes the following arguments:

  • component: The actual React Component to convert to into a Gutenberg block. Required.
  • metadata: Metadata object that contains several fields:
    • blockJson: The block.json object that describes the component attributes. Required.
    • edit: Provide a custom Edit function that describes the structure of your block in the context of the editor. Optional.
    • save: Provide a custom Save function that defines the way in which the different attributes should be combined into the final markup. Optional.

User Experience

The registerFaustBlock function will autogenerate custom edit and save functions that the Gutenberg Editor requires to register a Block.

Upon Component Registration, the Gutenberg Editor by default and using the blocks block.json will create editor fields that the user will be able to fill in.

Here is a screenshot of the above React Component that contains 3 fields. A textarea and two color pickers:

Screenshot 2023-08-03 at 10 53 27

By default, it will create relevant fields in the Form Editing View. In this mode, users will be able to fill in the component information using the generated control fields.

However, the developer has the option to provide hints to the registration function to assign certain fields on the sidebar asInspectorControls. Here is the same block with the two color controls located in the sidebar:

Screenshot 2023-08-03 at 10 55 58

Users would be able to switch between the form editing view to the presentational view using a preview button:

Screenshot 2023-08-03 at 11 49 38

Block Fields Configuration

This section is dedicated to provide a more detailed view of the available options to configure the default UX when using registerFaustBlock helper and to customise the block in the Editor according to your needs.

block.json fields

The registerFaustBlock helper will parse the respective block attributes and associate them with Editor Fields. The corresponding table represents the mapping logic between the block attributes and the associated fields:

type field comment
string TextControl Renders a TextControl field of type text
boolean RadioControl Renders a RadioControl field
integer TextControl Renders a TextControl field of type number
number TextControl Renders a TextControl field of type number
object TextAreaControl Renders a TextAreaControl field

Note: The array type won't be supported initially.

Labels

Each form field will consist of a Label and the associated control type. By default, the attribute name will be used as a label. Users would be able to override the default label by using the label property in the editorFields configuration object. (See below).

Ordering

Each form field will be rendered by their order they were defined in the block.json attributes list. Users would be able to override the default ordering by using the order property in the editorFields configuration object. (See below).

Providing hints with the editorFields configuration.

Since by default, when using the block.json supports only a limited types of fields, users would be able to provide more hints and configuration metadata to configure both the type of the control fields and their location. For example as mentioned in the demo component above, users can declare that some of the fields should be located in the sidebar and using a more appropriate control type like a ColorPicker. This will enhance the default editing experience.

To provide hints when registering a React Component with registerFaustBlock, create a new property called editorFields and attach this to the React Component:

// MyFirstBlock.js
function MyFirstBlock({ style, className, attributes, children, ...props }) {
  ...
}

MyFirstBlock.config = {
	name: "MyFirstBlock",
	editorFields: { // new field added
		message: { // Assume that type: "text" since the 'message' attribute is  type: "string"
			label: "My Message", // Use a custom label instead of default attibute name
			control: "textarea", // Use **TextAreaControl**  instead of **TextControl**
		},
		bg_color: {
			type: "color", // Use **ColorPicker**  instead of **TextControl**
			location: "inspector" // Specify location as sidebar or **InspectorControl**
		},
		text_color: {
			type: "color",
			default: "#eeeeee", // Overrides default value.
			location: "inspector", // Specify location as sidebar or **InspectorControl**
		},
	},
};

Here the component config section contains the new editorFields property that provides hints to the helper when building the final fields configuration. The associated field names should match the one with the block.json attributes. Each field will be merged together to form the final field configuration.

For example the first field message will have the final configuration:

"message": {
   type: "string",
   control: "textarea",
   default: "",
   label: "My Message",
   location: "editor"
}

This will create a TextAreaControl field with a default value of "", a label "My Message" and located in the Form Editor side.

Similarly the rest of the fields:bg_color and text_color will render a ColorPicker component and be located in the InpsectorControls area (block sidebar).

Available Controls

The registerFaustBlock helper with use conventional methods to merge block.json attributes and editorFields to create the final configuration for constructing the respective Editor Fields and InspectorControls.

The following control types will be available:

control field comment
color ColorPicker Renders a ColorPicker field
text TextControl Renders a TextControl field of type text
textarea TextAreaControl Renders a TextAreaControl field
radio RadioControl Renders a RadioControl field
select SelectControl Renders a SelectControl field
range RangeControl Renders a RangeControl field
number TextControl Renders a TextControl field of type number
checkbox CheckBoxControl Renders a CheckBoxControl field

Invalid combinations

We may chose to ignore certain invalid combinations of control types and attribute types. For example using type: "boolean" and control:"color".

In such cases it will fallback to regular TextControl with a warning.

Drawbacks

One potential drawback is that, depending on the complexity of the React component, the conversion process may not be straightforward. Some React components may rely on context or hooks that don't have a direct equivalent in Gutenberg. In such cases, you have the option to provide your own Custom Edit or Save functions when using the registerFaustBlock helper function.

Another caveat is that since we are registering the components as static blocks, then any use of useEffect, useState hooks are not allowed inside the component. See related SO answer.

However we could alleviate this fact by leveraging the Interactivity API in future iterations of this proposal.

In the meantime we will propose effective workarounds and limitations when using the components that would be compatible out of the box with our solution.

How to Contribute

Interested in hearing your thoughts on this and any possible enhancement's that you want to see materialised. Do the conventions make sense?

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

Status

✅ Closed

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions