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 concept, 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.
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’s. 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 kid 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…
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.
That 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:
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.
Source: https://deliciousbrains.com/develop-wordpress-plugin-webpack-react-rest-api-part-2/