Wordpress Gutenberg: React components on front end
Asked Answered
A

2

12

Gutenberg is still pretty new, but I'm still hoping someone has encountered this issue and found a solution.

I've used create-guten-block to boilerplate a project and created a test block. The problem I'm running into is that when I try to use a React component to modify state on the front-end, nothing happens. The components load fine through save(), but when you try to do something simple like toggle a list, the front-end remains unresponsive to the state changes. I'll also note that create-guten-block doesn't load any front-end JS, so I swapped the compiled javascript to load on the front end and still haven't been able to get it working.

Here's some code I pulled from Codecademy as an easy example to test with. When you select a name, it changes the text in sibling.js to display the name. The code works fine in create-react-app but does nothing on the front-end as a block in Gutenberg:

block.js

import { Parent } from './parent';

// More code here 

save: function( props ) {
    return (
          <div>
              <Parent />
          </div>
     );
 },

parent.js

import React from 'react';
import { Child } from './child';
import { Sibling } from './sibling';

export class Parent extends React.Component {
    constructor(props) {
        super(props);

        this.state = { name: 'Frarthur' };

        this.changeName = this.changeName.bind(this);
    }

    changeName(newName) {
        this.setState({
        name: newName
        });
    }

    render() {
        return (
        <div>
            <Child onChange={this.changeName} />
            <Sibling name={this.state.name} />
        </div>
        );
    }
};

child.js

import React from 'react';

export class Child extends React.Component {
    constructor(props) {
        super(props);

        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(e) {
        const name = e.target.value;
        this.props.onChange(name);
    }

    render() {
        return (
        <div>
            <select
            id="great-names"
            onChange={this.handleChange}>

                <option value="Frarthur">Frarthur</option>
                <option value="Gromulus">Gromulus</option>
                <option value="Thinkpiece">Thinkpiece</option>
            </select>
        </div>
        );
    }
}

sibling.js

import React from 'react';

export class Sibling extends React.Component {
    render() {
        const name = this.props.name;
        return (
        <div>
            <h1>Hey, my name is {name}!</h1>
            <h2>Don't you think {name} is the prettiest name ever?</h2>
            <h2>Sure am glad that my parents picked {name}!</h2>
        </div>
        );
    }
}
Asuncion answered 9/8, 2018 at 15:41 Comment(0)
L
33

TLDR;

Blocks are rendered into static HTML markup which should be manipulated like any other HTML markup that is received from the server.

Here is how it is done:

Gutenberg blocks are graceful shortcodes like the following code. That is how they are kept in database:

<!-- wp:paragraph {"key": "value"} -->
<p>Welcome to the world of blocks.</p>
<!-- /wp:paragraph -->

They are rendered into different visual representations for server side and client side code.

On the server serve side, these shortcodes are rendered into react components by the editor for visual representation. Editor uses the outer HTML comment for matching a shortcode to a react component.

On the client side, outer html comments will be ignored and the inner HTML files will be rendered.

Remember the shortcodes, [gallery id="123" size="medium"] which could be rendered into visual representation by tinymce plugin and you would see fully formed and functioning gallery in the editor window. The idea is same, ony this time Gutenberg editor renders slightly different shortcodes into a visual representation.

Now, the confusion arises because WordPress docs addresses these visual representations also as blocks. But source of truth for the whole rendering-serializing-parsing-re-rendering cycle is one and that is these so called "graceful shortcodes", rest is different forms and representations these shortcodes take. Say, in editor context, its is a rendered react component, on front end it is just regular html.

It is the return element of edit function that will determine how a block will appear in the editor window:

<!-- language: lang-js -->
registerBlockType(«NAMESPACE/BLOCK_NAME», {
    edit: function(props){
        // Should return a react element
    }
})

It is paramount to understand a block's life cycle to understand them better. Lets start from the beginning:

When you click a block icon in components panel, return of save function will be rendered and serialized and inserted into the page.

<!-- language: lang-js -->
registerBlockType("NAMESPACE/BLOCK_NAME", {
    save: function(props){
       // Should return a react element
    }
})

save function should return a react element, this element will be rendered by react and serialized by the block serializer and inserted into to the post content as a block. You can check serializer.js for how React element serialized into a block1.

<!-- wp:image -->
<figure class="wp-block-image"><img src="source.jpg" alt="" /></figure>
<!-- /wp:image -->

If it is dynamic block, save function will return null, so there will no content. Block will look something like this:

<!-- wp:latest-posts {"postsToShow":4,"displayPostDate":true} /-->

Notice the self closing comment:

In blocks grammar, the first one is called a static block (Block_Balanced) and the second one is dynamic block (Block_Void).

It is important to note that static blocks contain rendered content and an object of attributes. For dynamic blocks, render_callback should be supplied to register_block_type function during block registration.

So, when the_content is requested, server gets the_content and passes it through several filters before responding the request.

During this phase, attributes will be strip off from static blocks and content will be returned since static blocks have their contents already in themselves. For dynamic blocks, render_callback will be invoked and its return value will be returned as block content. That's what they mean by somehow fully-isomorphic3 in the documentation. You can check out render_block function in Wordpress core.

When you edit the block via gutenberg's visual elements, the block will go through re-serialization process all over again and a new visual representation will be drawn onto page based on changes you've made.

<!-- wp:paragraph {"key": "value"} -->
<p>Welcome to the world of blocks.</p>
<!-- /wp:paragraph -->

Once you click the publish button, this serialized data or row data, as the documentation tells, will be save to database.

Lets say you close the page after saving. When you opened it next time, saved block will be parsed by the block parser and visual representation will be drawn onto page. You can take a look at the parser2.

During parsing, block markup will be validated against the save function. If you changed the return of save function between two edits, previously saved block markup will be invalid or deprecated. You can update deprecated code by providing upgrade path in your block setting in registerBlockType. However you change the edit function without repercussions since it controls how blocks will appear on editor screen.

Upgrade path is just an array of objects with functions and properties. Deprecated block will be checked by each element on this array, based on priority and will be migrated if block is compatible with new version, if not old version will be returned.

Now coming to your question, when request a page on the front end server will send you fully formed html. So, on the front what you get is is static html, not a react element.

So, in reality save function has nothing to do with frontend other than creating static html wrapped in block comments <!-- wp:image --><!-- /wp:image -->, which happens while editing content. It is never run or consulted when serving the_content on the front end.

In order to add interactivity, you have to write code specifically targeting the front-end, just as you did before Gutenberg.

You can see the_content in its plain text form by switching to Code Editor in the editor window using More tools and options button, vertical ellipses next to update button.

From the front-end perspective, there is no difference whether you use tinymce editor or Gutenberg editor or plain html when creating the_content.

It is up to you enqueue another javascript file or use the one you enqueued via enqueue_block_assets when registering block.

To use React, you have to mount your component onto document using ReactDOM. Good thing is Wordpess already provides React and ReactDOM in global space but you need to indicate the dependency when enqueuing the script.

Lanie answered 28/1, 2019 at 22:30 Comment(6)
Apologies for taking so long to mark this as the best answer. Thank you for being so thorough!Asuncion
Can you give example how to mount the component onto document using ReactDOM ? I have similar problem, where in the save method, I want to use React and catch events like onClick.Hallucinosis
@Hallucinosis Sure. const element = <h1>Hello, world</h1>; ReactDOM.render(element, document.getElementById('root')); Make sure you have element with id attribute as 'root' in your html file. You have more detailed explenation here reactjs.org/docs/rendering-elements.html. If you are using gutenberg with wordpress the usual way, react may not be good idea.Lanie
@SnnSnn this should be put in the save function ? I need to catch events on the frontend (where users should click a button), not in the editor!Hallucinosis
@Hallucinosis If you have save function, it means block content will be rendered on the front end already, just use vanilla js or jquery, like you did before.Lanie
@SnnSnn I am new to gutenberg blocks and especially to create-guten-block. Can you give me example please?Hallucinosis
Z
9

It is my understanding that Gutenburg only supports interactivity on the editor side of the equation. Yes, you define the view side in React, but this view is rendered to static markup at the point the post is saved, and then only this static markup is delivered to the client (readers).

The benefit is you get "server side rendering" for free, and your client doesn't need React to view the post. The downside is if you want to do client side interactivity, its outside the scope of Gutenburg, and you'll have to find your own way.

Zacheryzack answered 5/11, 2018 at 12:17 Comment(2)
Thanks Bessey! I've gotten a good handle on it in the last three months and just provide front-end interactivity with jQuery. Not ideal, but already enqueued by Wordpress and it works.Asuncion
Can you give example how to mount the component onto document using ReactDOM ? I have similar problem, where in the save method, I want to use React and catch events like onClick.Hallucinosis

© 2022 - 2024 — McMap. All rights reserved.