Develop your own WordPress block - Part 4: Add input fields with RichText components

In this series of articles, I'll explain how you can build your own WordPress block from scratch. In the fourth part we look at how we can add editable fields with the RichText component in our WordPress block and display them in the frontend.

At the start of the series and first part is here .

The RichText component at a glance

In the current state of our WordPress block, title and description are output statically:

 <h2>Titel des Blocks</h2> <p>Beschreibung des Blocks.</p>

We want to make these two fields editable for the user in Part 4, so that the title and description can be entered directly in the block. We can implement input or text fields in the block with the RichText component of the Gutenberg Editor, which we receive from the wp.editor package.

 const { RichText } = wp.editor;

The RichText component can be rendered in the JSX code of the return statement of the edit function and then outputs an editable field with the HTML contenteditable attribute.

 <RichText tagName="p" value={ variable } className="css-class" onChange={ changeFunction } placeholder={ __( 'Placeholder Text', 'themecoder-block' ) } />

The component accepts several properties. It is onChange to have value for the current value or content of the text field and onChange , with which a function is transferred that is responsible for saving the content when the text in the input field is changed.

A number of other properties can optionally be defined, including HTML tag (p, h2, div), CSS class and placeholders. You can find a full list of props in the Github repo .

Add block attributes for text fields

Before we build the RichText component into the block, we first create two new attributes for title and description. These are defined in the attributes object in the Settings block.

 /** * Register block */ registerBlockType( 'themecoder-block/image-card', { title: __( 'Image Card', 'themecoder-block' ), category: 'layout', attributes: { title: { type: 'string', source: 'html', selector: '.tc-title', }, description: { type: 'string', source: 'html', selector: '.tc-description', }, }, // Edit-Function + Save Function } );

Our text fields are saved directly in the block's HTML markup. We therefore specify the type type as string and the source source as html for the associated attributes. With selector we determine exactly where our attributes can be found in the HTML markup.

Important to know: Basics of block attributes

1) Block attributes are a central part of every WordPress block and represent and structure all data (content, options, styling) in the block.

2) Blocks are not saved individually in the WordPress database, but end up as HTML markup with all other blocks of a post_content as post_content in the wp_posts table.

3) Blocks are separated from each other using HTML comments. When the Gutenberg Editor is called, individual blocks are generated from the entire HTML.

4) With the help of the defined block attributes and their selectors, the Gutenberg Editor extracts the variables from the HTML code and saves them in an object tree.

5) If attributes do not have a selector, they are saved in the HTML comment of the respective block instead of in the markup.

Example:

 <!-- wp:paragraph {"align":"center"} --> <p style="text-align:center">Beispiel-Text</p> <!-- /wp:paragraph -->

A paragraph block is displayed with the above HTML code, recognizable by the block comment <!-- wp:paragraph --> . Two attributes are also stored. First the content with the selector p in the markup, second the text alignment align: center in the comment.

Incorporate RichText component in the edit function

After registering the attributes, we can now incorporate the RichText component in the edit function of the block. We change the previous code as follows:

 edit( props ) { const { attributes, className, setAttributes, } = props; function changeTitle( newTitle ) { setAttributes( { title: newTitle } ); } function changeDescription( newDescription ) { setAttributes( { description: newDescription } ); } return ( <div className={ className }> <div className="tc-columns"> <div className="tc-image"> </div> <div className="tc-card"> <RichText tagName="h2" value={ attributes.title } className="tc-title" onChange={ changeTitle } placeholder={ __( 'Add a title…', 'gt-blocks' ) } keepPlaceholderOnFocus /> <RichText tagName="p" value={ attributes.description } className="tc-description" onChange={ changeDescription } placeholder={ __( 'Write a description…', 'gt-blocks' ) } keepPlaceholderOnFocus /> </div> </div> </div> ); },

Okay, let's take a closer look at the function line by line.

At the beginning we use Object Destructuring again to extract some variables from the props of our block. For our input fields we now also need the attributes object and the setAttributes function, with which attributes can be changed.

Then we define two functions changeTitle () and changeDescription () , which are responsible for changing the respective block attributes title and description as soon as the user manipulates the content in the input field.

The final change is to replace our static title and description with two RichText components. We pass our block attributes as value , our functions with onChange . For the title we use h2 as the tagName , for the description p .

The above code works, but with a little ES6 magic we can reduce the syntax a bit. Instead of laboriously creating a separate function for each text field, we use an arrow function directly in the RichText component to call setAttributes .

 <RichText tagName="h2" value={ title } className="tc-title" onChange={ ( value ) => setAttributes( { title: value } ) } placeholder={ __( 'Add a title…', 'gt-blocks' ) } keepPlaceholderOnFocus /> <RichText tagName="p" value={ description } className="tc-description" onChange={ ( value ) => setAttributes( { description: value } ) } placeholder={ __( 'Write a description…', 'gt-blocks' ) } keepPlaceholderOnFocus />

The functions changeTitle () and changeDescription () are no longer necessary.

Adaptation of the save function with RichText.Content

Finally, the content of our input fields must also be output in the front end with the save function. We also use the RichText component for this; the output is done here with RichText.Content . As before, the attribute is passed as a value .

 save( { attributes } ) { const { title, description, } = attributes; return ( <div> <div className="tc-columns"> <div className="tc-image"> </div> <div className="tc-card"> <RichText.Content tagName="h2" className="tc-title" value={ title } /> <RichText.Content tagName="p" className="tc-description" value={ description } /> </div> </div> </div> ); },

It is very important to specify the CSS class with className , which must match the selector value of the attribute. If the CSS classes do not match the defined selectors of the block attributes, the attributes can no longer be filtered out of the HTML code.

Invalidation Error in the Gutenberg Editor

After changing the save function you will see this error in the Gutenberg editor:

Gutenberg Editor: Unexpected content

The Invalidation Error always occurs when the saved HTML markup of the block does not match the markup defined in the Save function. You will therefore often see this error message when developing custom blocks, namely whenever you modify the save function.

For the user, this error should of course never appear, which is why extra precautions must be taken when updating the block markup to update blocks without errors. But that's enough topic for an independent article.

Result and outlook for part 5

And that's it for the fourth part. The block looks almost the same, but the title and description can now be entered by the user. After adding the block, the placeholders are displayed:

Image Card Block Part 4 You can find the complete sample code for our block in this Github repo: https://github.com/Netzberufler/themecoder-block

In the next part we will look at creating toolbar and sidebar options to customize the styling and layout of the block.

Overview of all articles in this series