When I created my Novo-Map WordPress plugin, I always found the information I needed fairly easily on the web. WordPress is such a popular tool that when I got stuck on something, I would usually just google it a little to easily find an answer to my question.

But the arrival of Gutenberg is changing the way WP plugins and themes are developed. This is obviously a good thing because it integrates modern technologies such as react.js or redux.js to WordPress. But Gutenberg is developing and changing at such a fast pace that it’s sometimes hard to keep up… and most of all, I find that there is still quite little information online about Gutenberg (and react/redux) development so I’ve sometimes been stuck for quite a long time on some topics.

Anyway, as I was thinking about updating my WordPress plugin for Gutenberg at the beginning of this year, I thought I would also document a little bit the topics that gave me some troubles at the same time… hoping it could be useful to the WordPress developer community

Note: This article is quite technical and is mainly intended for developers who want to create plugins using the new technologies that Gutenberg provides.

Some tools I used to create my WP plugin

To fully understand this article and the problems I encountered, I need to quickly explain you the structure of my plugin.

Like many developers, I used the WordPress plugin Boilerplate (WPPB) as a basis to create my plugin in an organized, standardized and object-oriented programming (OOP) manner. The WPPB is a great starting point and if you want to know more about it, there are many online tutorials like this one.

My plugin is therefore organized in classes that manage objects and their methods (in my case maps, markers, infoboxes etc…). Each of these classes has a “manager” class that allows me to access, modify, save or delete these objects in custom database tables (so my plugin adds tables in addition to the 12 basic WordPress tables).

Before WordPress 5.0 and the arrival of Gutenberg, 95% of my plugin was written in PHP and I just needed a little bit of JavaScript (or Jquery) for a few details and to make it more interactive in the browser.

Gutenberg: a modern front-end application

So since December 2018, Gutenberg is the new default WordPress editor and react/redux have been integrated into WordPress core. Gutenberg is what is called a Single Page App (SPA), which is a modern application that runs directly in the browser and does not need to reload the page while it is running.

Gutenberg was therefore created based on React and Redux (while adding a small abstraction layer to better manage future changes of these tools’s APIs). Without going into details (and also because I’m not an expert about that yet), react allows to create reusable components and to easily update a part of the DOM “react”ing to some actions without reloading the whole page. As for redux, it allows to easily organize/share data and their state between the different components of the application.

Since Gutenberg is a front-end application (i.e. it runs in the browser without direct access to the database), it uses WordPress’ REST API to access and/or modify the data that are stored in the database.

Note: As you can see, Gutenberg introduces a lot of new features (react, redux and the whole tool stack you need to use like npm, webpack, babel etc…) and the learning curve is quite high for beginners with little experience in JavaScript like me. The purpose of this article is not to talk about all these tools in detail (I highly recommend you this course on Udemy that I followed myself for that) but about a problem I encountered in particular. By the way, as all these elements are still quite new to me, feel free to correct me in the comment section if I say something stupid.

My problem: accessing my tables in the database and having access to my data in my components

So when I created my first Gutenberg block for my plugin, I quickly faced a problem! I didn’t know how to access my plugin’s data (all info about maps, markers, infoboxes etc…stored in custom tables in the db) inside my components. Well I knew approximately how to do it and I thought I had to create custom Endpoints in the WordPress REST API to access (or modify) my data from my components.

But how could I do it the Gutenberg way (so react) and have access to my data inside my blocks?

I found very little information on the web about this topic and I found myself having to dig a lot in the Gutenberg source code to finally have a working solution, that I will detail below. In the following, I assume that you know how to create a plugin to save a basic Gutenberg block (although I will try to add some links to useful resources).

Create my custom Endpoints for the WP REST API

The first thing to do is to create the interface between my Gutenberg blocks on the client side and my PHP classes and the database on the server side. This interface will allow me to access my data using my existing classes and to expose them through the WP REST API.

In OOP this interface becomes an API class in which we use the rest_api_init hook and the register_rest_route() function to create our custom Endpoints as follows (with here an example of a method that retrieves a list of all the available maps):

<?php
/**
 * Class Api exemple
 */
class Api {
    private $version;
    private $namespace;
    private $plugin_dir_path;
    private $plugin_name;

    public function __construct($plugin_name) {
        $this->plugin_name = $plugin_name;
        $this->version   = '1';
        $this->namespace = $plugin_name . '/v' . $this->version;
        $this->plugin_dir_path = plugin_dir_path( dirname( __FILE__ ) );
    }

    public function run() {
        add_action( 'rest_api_init', array( $this, 'register_gmap_list' ) );
    }

    public function register_gmap_list() {
        register_rest_route(
            $this->namespace,
            '/my-unique-url',
            array(
                'methods'             => 'GET',
                'callback'            => array( $this, 'get_gmap_list' ),
                'permission_callback' => function () {
                    return current_user_can( 'edit_posts' );
                },
            )
        );
    }

    public function get_gmap_list() {
        require_once $this->plugin_dir_path . 'includes/class-gmap.php';
        require_once $this->plugin_dir_path . 'includes/class-gmap-manager.php';
        global $wpdb;
        $gmap_manager = new Gmap_Manager($wpdb);
        $gmap_list = $gmap_manager->get_list();

        return $gmap_list;
    }
}

if you use the WPPB, you will be able to initialize the API from the main class of your plugin as follows:

$api = new Api( $this->get_plugin_name() );
$api->run();

Some comments about this class and more particularly about the register_rest_route() function. So if you make a request to the 'namespace/my-unique-url' address (and you have the ‘edit_posts’ permission), the callback function get_gmap_list() will be activated and return a list of gmap objects in JSON format via the API.

As we use a JSON API (JavaScript Object Notation), I also recommend that you add to the class defining the objects you send via the API (in this case my Gmap class) the JsonSerializable interface that allows you to control how data is encoded in JSON:

class Gmap implements \JsonSerializable {
    /**
     * define all the properties of my class
     */

    /**
     * Define how the properties of my class are exported to JSON
     */
    public function jsonSerialize(){
        $vars = get_object_vars($this);
        return $vars;
    }
}

One more thing, you may need to pass some parameters to your Endpoint (for example an id for a map or whatever). The register_rest_route() function allows you to do this in the following way:

register_rest_route(
            $this->namespace,
            '/my-unique-url/(?P<id>\d+)/(?P<string>[a-zA-Z0-9-]+)',
            array(
                'methods'             => 'GET',
                'callback'            => array( $this, 'my_custom_callback' ),
                'args' => array(
                    'id' => array(
                        'default' => 0,
                        'required' => true,
                        'validate_callback' => function($param, $request, $key) {
                            return is_numeric( $param );
                        }
                    ),
                    'string' => array(
                        'required' => true,
                        'validate_callback' => function($param, $request, $key) {
                            // validate $param
                            return $param
                        }
                    )
                ),
                'permission_callback' => function () {
                    return current_user_can( 'edit_posts' );
                },
            )
        );

This way, if you make a request to the 'namespace/my-unique-url/id/string' address, you will be able to validate the sent parameters and use them in your my_custom_callback() function as follows:

public function my_custom_callback($request) {
    $id = $request['id']
    $txt = $request['string']
}

Using data from the API in my Gutenberg blocks and components

Well… so far I have to say that everything was going well for me since everything was done in PHP (as I said before, I’m in the PHP + a little bit of JS developers family… but I’m trying to learn JS deeply like Matt Mullenweg recommended back in 2015 already 😉 ). So now is when things get complicated for me. By the way, if you think I made a bad choice for one reason or another, don’t hesitate to leave me a comment.

I’m not going to go into details about how to register a new block in Gutenberg nor about the whole stack of tools that comes with it (there are plenty of tutorials like the course I recommended before that explain it very well)… But for this example I assume that you know how to create a plugin and register a block as follows:

import { registerBlockType } from '@wordpress/blocks';
import { __ } from '@wordpress/i18n';
import Edit from './edit';

registerBlockType( 'your-plugin-blocks/block-name', {
	title: __( 'Your block title', 'domain' ),
	description: __( 'Your block description', 'domain' ),
	category: 'widgets',
	icon: 'admin-network',
	keywords: [
		__( 'map', 'domain' ),
		__( 'novo-map', 'domain' ),
	],
	attributes: attrs,
	edit: Edit,
	save() {
		return <p>Saved Content</p>;
	},
} );

In this article, we will only focus on the Edit function which defines what happens in the editor when the block is used. It is precisely in the props (properties in react) of this function that we would like to have access to our plugin’s data.

Local state of a component vs global state of the application

Anyway now to access my plugin data stored in the db on client side, we could simply use the WordPress module api-fetch to retrieve our data with an API call from a component inside our edit function.

Yet it was while doing some research on this topic that I started to see things like HOC (higher-order components) withSelect or withDispatch appear and that I heard about the @wordpress/data module.

As Gutenberg is an application with a complex structure, it needs a module like wp.data (which is based on Redux but with some differences) to manage its global state and to organize/share its data within WordPress and the plugins. Thanks to this module, it is possible to easily access WordPress data from any component… but also to modify the state and reflect these changes within the whole application (that’s why it’s called a global state). To understand a little more how the wp.data module works, I suggest you to watch this video:

You see that thanks to wp.data, it is possible to access WordPress data with something like this (you can try this command in the console of your browser when you are logged in your admin):

wp.data.select('core/editor').selector()

This command allows you to access the 'core/editor' store and retrieve the data of your choice using a selector. And this module also allows you to register your own store and access its data in the same way from any component.

So the question now is:

Do I really need to use the wp.data module and register my custom “store” in my plugin?

The answer will depend on the complexity of your application. If you create a simple block that can manage the state of its data in a single component, you certainly don’t need to create your own store (so you can just use a local state). On the other hand, as far as my plugin is concerned, eventually I might have several blocks or components that will share data… and even one day the whole admin part of my plugin might be created in JavaScript… so it seemed more logical to register my custom store and that’s what I’m going to talk about in the following part.

Register our custom store in wp.data

Let’s face it, we could write several articles just about how wp.data (and thus redux) works and I suggest you to read this excellent series of articles if you want to understand in detail how this module works.

Below I will share with you my code with a couple of comments but without going into all the details (this article is already very long). So here’s how I registered my custom store that simply allows me to get a list of all my Gmap objects for the moment:

import apiFetch from '@wordpress/api-fetch';
import { registerStore } from '@wordpress/data';

const DEFAULT_STATE = {
	gmapList: {},
};

const STORE_NAME = 'plugin-name-blocks/store-name';

const actions = {
	setGmapList( gmapList ) {
		return {
			type: 'SET_GMAP_LIST',
			gmapList,
		};
	},
	getGmapList( path ) {
		return {
			type: 'GET_GMAP_LIST',
			path,
		};
	},
};

const reducer = ( state = DEFAULT_STATE, action ) => {
	switch ( action.type ) {
		case 'SET_GMAP_LIST': {
			return {
				...state,
				gmapList: action.gmapList,
			};
		}
		default: {
			return state;
		}
	}
};

const selectors = {
	getGmapList( state ) {
		const { gmapList } = state;
		return gmapList;
	},
};

const controls = {
	GET_GMAP_LIST( action ) {
		return apiFetch( { path: action.path } );
	},
};

const resolvers = {
	*getGmapList() {
		const gmapList = yield actions.getGmapList( '/namespace/my-unique-url/' );
		return actions.setGmapList( gmapList );
	},
};

const storeConfig = {
	reducer,
	controls,
	selectors,
	resolvers,
	actions,
};

registerStore( STORE_NAME, storeConfig );

export { STORE_NAME };

You can see that at the very bottom we use the registerStore() function to add our store with the name 'plugin-name-blocks/store-name' as the first argument and a complicated storeConfig object as the second argument, which defines the state of the data, how to access it and how to modify it. Let’s take a closer look at this 2nd argument to understand it better.

storeConfig has 5 elements:

  1. actions: Contains all the action creators that will tell to the reducer how it should modify the state. An action creator can also be used in combination with a resolver and a control to generate side effects.
  2. selectors: Contains all the selectors that allow to retrieve data from our store. A selector is simply a function that takes at least the current state as an argument and returns the part of the state we want to get. In this case getGmapList allows me to retrieve a list of all my Gmap objects.
  3. resolvers: These are objects that allow you to assign a function or a side effect to selectors. A resolver must have the same name as its corresponding selector. Even if it is not mandatory, they are often generator functions combined with a control.
  4. controls: Defines a logic associated with a certain type of action. They are particularly useful to be used from a resolver to obtain or save data in the database using the WP REST API. In this case, you can see that our control sends a request to the address defined in our API to retrieve the list of Gmaps in the resolver.
  5. reducer: Describes the shape of your data and how its state changes in response to actions dispatched to the store. The reducer always receives the previous state and an action object and returns the updated state.

I have to admit that I found the mechanics of an store quite complex and that I had to read and research quite a bit on this subject to understand the concept a little better. I can’t help but see similarities between this store and my manager class which manages the interface between my objects and the server side database with “getters” and “setters” functions. But in addition, a store allows to update all the components connected to it according to the current state of the data.

In short I suggest that you try to register your own store and play with it to better understand the concept. I probably still have a lot to discover about this myself.

Access the store’s data in my components

Now let’s say I want to display in my block a <select> tag containing all my Gmaps in its <option>name of my Gmap</option>. The first way to do this is to use the HOC withSelect to wrap our Edit function as follows:

import { Component } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { withSelect } from '@wordpress/data';
import { compose } from '@wordpress/compose';
import { Spinner } from '@wordpress/components';
import { STORE_NAME } from '../../stores/novo-map-gmap';

class Edit extends Component {

	render() {
		const { className, attributes, gmapList } = this.props;

		// return a Spinner while the maps are loading
		if ( ! Object.keys( gmapList ).length ) {
			return (
				<div className={ className }>
					<Spinner />
					{ __( 'Loading Maps', 'novo-map-blocks' ) }
				</div>
			);
		}

		// display the map selector
		return (
			<div className={ className }>
				<select>
					<option value={ 0 }>
						{ __( 'Choose a Novo-map', 'novo-map-blocks' ) }
					</option>
					{ Object.values( gmapList ).map( ( gmap ) => {
						return (
							<option key={ gmap.id } value={ gmap.id }>
								{ gmap.name }
							</option>
						);
					} ) }
					;
				</select>
			</div>
		);
	}
}

export default compose( [
	withSelect( ( select ) => {
		return {
			gmapList: select( STORE_NAME ).getGmapList(),
		};
	} ),
] )( Edit );

There are several things to consider in this Edit function:

  • Our Edit function is wrapped by the HOC withSelect which returns an object containing the props we would like to access inside our components. This is where we access our store using our selector. Thus, gmapList becomes accessible in our props inside our render function.
  • You will notice that I left the compose() around the withSelect. It allows you to compose several HOCs in one for readability reasons. In this simple example, we only use withSelect to access data from our store (so I could have removed the compose). But often we will also use withDispatch to modify the state of our store or other HOCs.
  • Finally you see that there are 2 returns inside our render. This is because our gmapList is still empty until the API request is fulfilled (if you look for the DEFAULT_STATE, you see that it is empty before our API request). So we display a spinner component indicating that the maps are loading. Then, once gmapList contains our maps, we display a select tag containing all our maps inside.

To conclude, I would just like to add that withSelect and withDispatch are not the only way to interact with our store. More recently, elements called “Hooks” have been introduced in React and the useSelect and useDispatch Hooks have been introduced in Gutenberg. They allow you to interact with the store without necessarily writing classes. You can learn more about it here.

Since it took me a long time to put all these elements together and I had a hard time to find this info online, I hope this blog post will help other devs in the WP community. In the coming weeks, I will continue to work on my plugin and I will document the issues I encounter that I think are relevant along the way. Don’t hesitate to leave me a comment if you have any remarks or questions!

References

Benoit Luisier

Self taught web developer and a travel addict , I share on Novo-Media my tips about the more "techy" part of topics like web development, Wordpress, Web optimisation or SEO. I'm also the creator of the google maps Wordpress plugin Novo-Map

Newsletter: Our latest posts in your inbox

Join the community now and get all our articles straight to your inbox! 0% Spam Guaranteed !

You have a question???

If it's directly related to this blog post, leave us a comment below, otherwise feel free to start a discussion on the forum!

Reader Interactions

Leave a Reply

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


Protected by reCAPTCHA / Privacy Policy - Terms of Service