How to Develop a WordPress Plugin Using Webpack 3, React and the REST API (part 2)

How to Develop a WordPress Plugin Using Webpack 3, React and the REST API (part 2)

Have you ever wondered how to get React working with the WordPress REST API? If so you’re in the right place – that’s what we’re going to cover in this follow-up to part one of how to develop a WordPress plugin! In our previous post, we explained what Webpack is and got it integrated into our WordPress sample plugin. We also got BrowserSync set up and reloading our app. In this part we’re going to look at how to get the React side of our plugin working with the WordPress REST API so that the plugin actually does something cool.

So let’s waste no time and get back into it.

In part one we set up a starter plugin called WP React Boilerplate. I’ve now updated this plugin to include the updates made in part two, so feel free to check it out.

Webpack updates

If you recall, in part 1 we went through and learned what Webpack is and how things like loaders and plugins work. If you’re not sure what any of those things are – go ahead and read part 1 first. We’re going to update our webpack.config.js file to include a few updates to work with SASS and have our CSS loaded from an external file.

You can check out the package.json git blame to see the updates, but we basically loaded in some SASS specific modules and the extract-text-webpack-plugin to better handle our styles.

The extract-text-webpack-plugin plugin is the go-to for working with Webpack and extracting styles to external files. It’s much easier to load in external styles and have them be cached in a CDN, for example. To get this running, in our webpack.config.js file we’ll add the following to the top:

const ExtractTextPlugin = require( 'extract-text-webpack-plugin' );

The next part of our webpack.config.js file we’ll update is the modules section to tell Webpack to use the extract-text-webpack-plugin to output our .css and .scss files, as well as use the sass-loader.

     {
            test: /\.css$/i,
            use: ExtractTextPlugin.extract( {
                fallback: 'style-loader',
                use: 'css-loader'
            } ),
        },
        {
            test: /\.scss$/i,
            exclude: /node_modules/,
            use: ExtractTextPlugin.extract( {
                use: [ 'css-loader', 'sass-loader' ]
            } )
        },

Then finally we’ll make sure the plugin is loaded when Webpack runs:

plugins: [
    ...
    new ExtractTextPlugin( {
        disable: false,
        filename: 'style.bundle.css',
        allChunks: true
    } ),
]

In the above snippet you can see that the file output by the extract-text-webpack-plugin is style.bundle.css. This gets chucked into our dist folder with the JavaScript bundle.js. We’ll add a wp_enqueue_style() call to the plugin so that the CSS file is loaded in.

wp_enqueue_style( $this->plugin_domain . '-bundle-styles', plugin_dir_url( __FILE__ ) . 'dist/style.bundle.css', array(), $this->version, 'all' );

W00t! We now have the ability to use SASS in our plugin, which will let us use syntactic sugar like @imports, @mixins, variables and nesting. We also have our styles being loaded from external files. Sweet.

WordPress REST API

Next up we’ll go over how the REST API is set up to work with the plugin. We’ve covered the REST API here in the past, so I won’t get into too much detail. I will be honest with you however, this part took a lot more effort than I thought it would 😩.

To give you an idea, this is what we’re going to build:

The first thing we’ll need to add to make our plugin functional is add some REST API endpoints. The REST API documentation isn’t great, but it covers the basics on what you need to know to create a custom endpoint. To be honest I ended up reading through the code to get a better understanding of the various methods, so you may want to do that if you need some more information.

In our case we’re going to create a super basic and really ugly React based database editor for the wp_options table. In order to do that we’ll need some routes and handlers to read and update options.

The first endpoint will give us a list of all the records in the wp_options table. You can see more of how the endpoint is set up in the server/wprb-rest-server.php file. But essentially, setting up that endpoint looks like this:

    $namespace = $this->namespace . $this->version;

    register_rest_route( $namespace, '/records', array(
        array(
            'methods'             => WP_REST_Server::READABLE,
            'callback'            => array( $this, 'get_options' ),
            'permission_callback' => array( $this, 'get_options_permission' )//Remove to leave open
            ),
        ) );

The register_rest_route() function is the meat and potatoes of the REST API. What you do is set the base URL for the endpoint – something like http://plugins.dev/wp-json/wprb/v1 – and then set the path for the endpoint. In this case it is /records.

The next argument is a configuration array. The methods key lets you set which HTTP request method the endpoint should listen to. These can be GET, POST, OPTIONS etc. The REST API has helper variables for these, so in this case WP_REST_Server::READABLE is just ‘GET’. You can see a list of the options in the WP_REST_Server class.

The callback key lets you define a, you guessed it, callback function. And the permission_callback lets you define a method to check that the user making the current request is allowed. I found this to be a little painful using a local MAMP server, but we’ll get into that later.

So for the /records endpoint we need to output a list of all the records in the wp_options table. Luckily WordPress has the wp_load_alloptions() function, so we’ll use that.

public function get_options( WP_REST_Request $request ) {
    return wp_load_alloptions();
} 

To test that our Rest API is working, we’ll need to use a REST API client. You could use cURL, but if you’re feeling like using something that’s a little easier on your eyeballs, I’d suggest Insomnia or Postman. I’m using Postman here, but Insomnia works just as well.

Postman

As you can see the /records endpoint returns all our options in JSON format, so we’re off and running.

Up next we’ll have to create a route to edit a record. To do that we’ll create an endpoint with a regular expression that listens for HTTP POST requests.

register_rest_route( $namespace, '/record/(?P<slug>(.*)+)', array(
        array(
            'methods'             => WP_REST_Server::READABLE,
            'callback'            => array( $this, 'get_option' ),
            'permission_callback' => array( $this, 'get_options_permission' )
        ),
        array(
            'methods'             => WP_REST_Server::EDITABLE,
            'callback'            => array( $this, 'edit_option' ),
            'permission_callback' => array( $this, 'get_options_permission' )
        ),
    ) );

In this route definition you can see that we’ve got a slightly different route of /record/(?P<slug>(.*)+). It looks more complicated than it is. The P<slug> part is what you can use in the callback method to grab the value of <slug> in our code. Otherwise, the rest is normal PHP PCRE regular expression syntax, so you can match whatever you’d like. I personally wanted the slug of the option to be passed via the URL, so that’s what this does.

There’s two definitions here for two different HTTP methods. WP_REST_Server::READABLE as before, is for GET requests. We’re not going to use this, but I left it in for completeness. WP_REST_Server::EDITABLE handles POST, PUT, and PATCH requests, with POST being what we’re looking for.

The callback we need to update an option needs to do a couple things. Check if the option exists in the database, check that all the data we need has come through, and finally update the option.

public function edit_option( WP_REST_Request $request ) {
    $params = $request->get_params();

    if ( ! isset( $params['slug'] ) || empty( $params['slug'] ) ) {
        return new WP_Error( 'no-param', __( 'No slug param' ) );
    }

    $body = $request->get_body();

    if ( empty( $body ) ) {
        return new WP_Error( 'no-body', __( 'Request body empty' ) );
    }

    $decoded_body = json_decode( $body );

    if ( $decoded_body ) {
        if ( isset( $decoded_body->key, $decoded_body->value ) ) {

            if ( ! get_site_option( $decoded_body->key ) ) {
                return false;
            }

            if ( update_option( $decoded_body->key, $decoded_body->value ) ) {
                return true;
            }
        }
    }

    return false;
}

Luckily the WordPress options API has a few helper functions, like update_option() which will edit or add an option if for some reason it doesn’t exist. You’ll notice that we’re parsing JSON from the request body, we’ll see how that gets sent over shortly!

And with that we can now load our options and edit our options using the REST API. Now lets see how we can access this API from our React front-end.

React Part Deux

In part 1 we grazed the surface of setting up React with Webpack. We’re now going to get our hands dirty and actually get a functioning mini-app running with React.

Back in src/components/App.js we’re going to set up everything to make our React app run.

The first thing we have to do is get all the options from the database using our REST API endpoint. We’ll use the React lifecycle method, componentDidMount() to load in all the options via AJAX on load.

    jQuery.ajax({
        url: AJAX_BASE + '/records',
        dataType: 'json',
        method: 'GET',
        beforeSend: function ( xhr ) {
            xhr.setRequestHeader( 'X-WP-Nonce', window.wpApiSettings.nonce );
        },
        success: function(data) {
            this.setState( { options: data } );
        }.bind(this)

      });

In the example app we’re using jQuery’s AJAX method, since jQuery’s already loaded on the page. But you could just as easily use whichever AJAX library you’re comfortable with.
Once we’ve got a successful response from the API we assign the resulting JSON to React’s state.

At the bottom of our render() function we basically set up an HTML table to output that list of our options. In a real world application you would want to paginate this list, but for now it accomplishes what we need.

render() {
    const items = Object.keys( this.state.options ).map( key =>
        <tr key={key}>
            <td>{key}</td>
            <td>
                <p>
                    <span className="edit-me">{this.state.options[ key ]} | <a href="#" onClick={( e ) => this.toggleVisible( e, key )}>Edit ✏️</a></span>
                    <span className={this.state.saved[ key ] ? 'saved check' : 'check'}>✅</span>
                </p>

                <EditForm
                    handleSubmit={this.saveItem}
                    handleChange={this.handleChange}
                    key={key} id={key}
                    value={this.state.options[ key ]}
                    visible={this.state.visible[ key ]}
                    hideItem={this.toggleVisible} />
            </td>
        </tr>
    );

    return (
        <div className="wp-react-boilerplate">
            <h1>WP React Boilerplate</h1>
            <h4>Options Editor</h4>
            <table>
                <tbody>
                {items}
                </tbody>
            </table>
        </div>
    );
}

Like all React applications, UI interactivity is managed by the application state. Our app is no different. The items variable runs a map() function on the this.state.options object which holds all the options we grabbed from the REST API earlier. We then add some HTML to hide/show the edit form and reference an EditForm component which actually holds the form to edit the record.

Components and Props

Components and props are essential to building modular React apps. From the documentation:

Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.

Props let you pass data from parent to child component in one direction, which is one of the key aspects to working with React. Parent components can send anything to a child via props: values, objects, and even functions.

We definitely want to re-use the edit form for each record in our table, so it makes sense to break it out into a component.

In src/components/EditForm.js you’ll find a basic React component that consists of a simple HTML form.

handleSubmit( event ) {
    event.preventDefault();
    this.props.handleSubmit( this.props.id );
}

render() {
    return (
        <form onSubmit={( e ) => this.handleSubmit( e )} className={this.props.visible ? 'visible' : 'hidden'}>
            <label>
                <input type="text" value={this.props.value} onChange={this.handleChange} />
            </label>
            <br />
            <input type="submit" value="Save" />
        </form>
    );
}

React goes back to the days of setting event handlers on DOM elements…

”onclicks

The onSubmit() handler calls a local function that in turn calls the handleSubmit() method that was passed to the component via a ‘prop’. This way our App component acts as the single source of truth and handles the manipulation of state.

Back in src/components/App.js we have our saveItem() method that does the actual post to our REST API endpoint.

saveItem( key ) {
    const val = this.state.options[ key ];
    const post_data = {
        key: key,
        value: val
    };

    jQuery.ajax({
        url: AJAX_BASE + `/record/${key}`,
        dataType: 'json',
        method: 'POST',
        data: JSON.stringify( post_data ),
        beforeSend: function ( xhr ) {
            xhr.setRequestHeader( 'X-WP-Nonce', window.wpApiSettings.nonce );
        },
        success: function(data) {
            if ( true === data ) {
                const saved = this.state.saved;
                saved[ key ] = true;
                this.setState( { saved } );

                //HACK to hide 'saved' checkmark
                setTimeout( () => {
                    saved[ key ] = false;
                    this.setState( { saved } );
                }, 1200 );
            };
        }.bind(this)

      });
}

Once we know all was good with the AJAX request we update the state and show the fancy green checkmark ✅.

REST API and Permissions

The last thing I’ll mention is how the REST API works with permissions and authentication. According to the documentation, the default method is cookie-based authentication with nonces acting as verification. In our React app, we pass a nonce as a the X-WP-Nonce header in jQuery’s beforeSend() method.

This is ok if you’re already logged in to WordPress, but if you want to access the API without being logged in, you’ll need to use other authentication methods listed in the documentation, such as JSON Web Tokens or Oauth.

As mentioned on the REST API FAQ, to get authentication working you may also need to add the SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 rule to your vhost directive in Apache. On MAMP I added this here:

MAMP settings

Next Steps

And with that, we’re done. What a ride. Turns out making an administration interface, even a basic one, is a lot of work!

In this second part we went over how to get the REST API set up with some custom endpoints. We also saw how we can use React to make our UI snappy and query our API to get and update database records. Neat! Next steps could be to edit another table in the database, add delete functionality, and probably make the UI a bit prettier.

Have you worked with React and the WordPress REST API before? Did you use a different method for authentication? Let us know in the comments.

About the Author

Peter Tasker

Peter is a PHP and JavaScript developer from Ottawa, Ontario, Canada. In a previous life he worked for marketing and public relations agencies. Loves WordPress, dislikes FTP.

  • darrencooney

    Nice write up Peter, thanks for sharing.
    I just launched v2 of Instant Images (https://wordpress.org/plugins/instant-images/) built with Webpack, React and the REST API – and you’re right it’s a lot of work 🙂

    For authentication, I used the simple Cookie authentication as found in the docs.
    xhr.setRequestHeader(‘X-WP-Nonce’, wpApiSettings.nonce);

    One challenge I did come up against and was not able to solve was I wanted to use the JavaScript Fetch API for my REST API calls but authentication was failing and couldn’t figure out why.

    I ended up using a send() request instead of using jQuery. Did you try anything other than jQuery for your REST API interactions?

    • Peter Tasker

      Funny you mention using Fetch(), the initial draft of the article had Fetch() as well. I also gave up because I got fed up with trying to get it to work! The issue is that Fetch doesn’t send the cookie authentication headers, but jQuery does. If you take a look at the XHR request in the Devtools you’ll see what I mean. It could be that the credentials option needs to be set: http://jasonmerino.me/til-sending-cookies-with-fetch-api

      • darrencooney

        Interesting. Glad to know I wasn’t alone here. ha

      • This I how I got fetch working with the REST API.

        `fetch( url, { credentials: ‘include’, headers: { ‘X-WP-Nonce’: config.nonce } } )`

  • Nick Barrett

    “React goes back to the days of setting event handlers on DOM elements”

    Probably worth mentioning you’re not *actually* setting handlers on DOM elements like the old days, its just how the JSX *looks* but actually compiles into plain JS objects.

  • Kishore Sahoo

    what is the best way to return error message? Can we send error code like 401,402 for failure ? then how we can read in that response in our android or IOS app? My mobile developers tell that he needs always status 200 or 201.

  • yoshiboarder

    Do you have plan to post about WordPress Theme with Webpack and React.js?