Building a WordPress plugin with React – Part 1

In this tutorial series we’re going to be building a simple React contact form plugin using our WP Reactivate boilerplate. We will be utilising all 3 entry points – settings page, widget and shortcode – and by the end you should have a decent understanding of how to use WP Reactivate to build React apps in your WordPress sites.

Note that this not a tutorial on how to use React, but rather an introductory guide on using the WP Reactivate boilerplate. Before getting started, it is important that you are familiar with React and ES6, as well as the latest version of the WP Reactivate boilerplate. At the very least, ensure that you have read through the Quick Start section of the React docs and the WP-Reactivate README.

This tutorial project is on GitHub, and we have included links to view the necessary changes at every step of the way. Be sure to check out these links as you go through the tutorial, and pay attention to the diffs and inline comments to see exactly what we’re changing and why.

Getting Setup

Firstly, download the latest version of WP Reactivate from GitHub, and extract it to a new folder in /wp-content/plugins/. For the sake of this tutorial, let’s call that folder ‘WPR-Contact-Form’. Now let’s rename wp-reactivate.php to wpr-contact-form.php, open it, and change the plugin name to “WPR Contact Form”:

<?php
/**
 * WPR Contact Form
 *
 *
 * @package   WPR Contact Form
 * @author    Pangolin
 * @license   GPL-3.0
 * @link      https://gopangolin.com
 * @copyright 2018 Pangolin (Pty) Ltd
 *
 * @wordpress-plugin
 * Plugin Name:       WPR Contact Form
 * Plugin URI:        https://gopangolin.com
 * Description:       Basic Tutorial on WP-Reactivate Usage
 * Version:           1.0.0
 * Author:            pangolin
 * Author URI:        https://gopangolin.com
 * Text Domain:       wp-reactivate
 * License:           GPL-3.0
 * License URI:       https://www.gnu.org/licenses/gpl-3.0.txt
 * Domain Path:       /languages
 */

In your own plugins you will more than likely want to update the rest of the information, and possibly change prefixes etc, but since that will not be the focus of this tutorial, we’re just going to change ‘Plugin Name’ and ‘Description’.

Now head to your site’s WordPress dashboard plugins page and activate the plugin.

Open up a terminal in the WPR-Contact-Form folder, and install the required packages with ‘yarn’.

Build your application with ‘yarn build’. Now, whenever you save changes in your React files, the app will recompile and the changes will reflect on your site.

Click here to see the changes for this step on GitHub.

Overview

Before moving on, let’s quickly run through what we’re going to build.

The contact form will consist of name, email and message fields, and a submit button. We will build this as a single React component, to be included in WP-Reactivate’s Widget and Shortcode React containers.

Our settings page will consist of a single ‘notification email address’ setting. On submission of the contact form, a notification email will be sent to that email address. We will also build a new React component for displaying admin notices on our settings page.

In this first tutorial of the series, we will focus on building the settings page. This will include:

  • Adding a single setting for the notification email address
  • Setting up a REST endpoint in order for our React components to interact with our WordPress data
  • Using our existing Admin.jsx React container to create a functional settings page for our plugin
  • Creating a new React component to display settings page admin notices

The PHP

Setting the option name

Firstly, we need to change our existing setting name from ‘wpr_example_setting’ to ‘wpr_contact_email’. The quickest and easiest way to do this is just run a search and replace within your entire WPR-Contact-Form folder. There are 3 files where the setting name should be updated:

/includes/Plugin.php (the activate() function runs when the plugin is activated, and adds the option if it doesn’t already exist)

/**
	 * Fired when the plugin is activated.
	 *
	 * @since    1.0.0
	 */
	public static function activate() {
		add_option( 'wpr_contact_email' );
	}

 

uninstall.php (deletes the option when the plugin is uninstalled – optional)

<?php
// If uninstall not called from WordPress exit
if ( !defined( 'WP_UNINSTALL_PLUGIN' ) )
  exit();

delete_option( 'wpr_contact_email' );

 

/includes/endpoint/Admin.php

/**
     * Get Example
     *
     * @param WP_REST_Request $request Full data about the request.
     * @return WP_Error|WP_REST_Request
     */
    public function get_example( $request ) {
        $example_option = get_option( 'wpr_contact_email' );

Do the same in the update_example() and delete_example() functions here as well.

Click here to see the changes for this step on GitHub.

Setting up the REST endpoint

WP-Reactivate comes with an example REST endpoint class (/includes/endpoint/Example.php). We will modify this class to create an ‘Admin’ endpoint, which will be used for getting, setting, updating and deleting our settings (or in the case of our tutorial plugin, just the single notification email address setting).

Now, rename the Example.php endpoint file to Admin.php, and change the class name from Example to Admin.

<?php
/**
 * WP-Reactivate
 *
 *
 * @package   WP-Reactivate
 * @author    Pangolin
 * @license   GPL-3.0
 * @link      https://gopangolin.com
 * @copyright 2017 Pangolin (Pty) Ltd
 */

namespace Pangolin\WPR\Endpoint;
use Pangolin\WPR;

/**
 * @subpackage REST_Controller
 */
class Admin {

 

Update the $endpoint value to our new endpoint, ‘admin’.

/**
     * Register the routes for the objects of the controller.
     */
    public function register_routes() {
        $version = '1';
        $namespace = $this->plugin_slug . '/v' . $version;
        $endpoint = '/admin/';

 

Then, in our base plugin file wpr-contact-form.php, update the $wpr_rest variable value on line 82 to point to our new Admin endpoint.

/**
 * Initialize Plugin
 *
 * @since 1.0.0
 */
function init() {
	$wpr = Plugin::get_instance();
	$wpr_shortcode = Shortcode::get_instance();
	$wpr_admin = Admin::get_instance();
	$wpr_rest = Endpoint\Admin::get_instance();
}
add_action( 'plugins_loaded', 'Pangolin\\WPR\\init' );

 

We also want to update the name of the parameter sent with our fetch statement in our React files from ‘example_setting’ to ’email’. We will of course need to update this in our React files as well, but let’s get the last of the PHP changes out of the way before we move onto that.

public function update_contact_email( $request ) {
    $updated = update_option( 'wpr_contact_email', $request->get_param( 'email' ) );

    return new \WP_REST_Response( array(
        'success'   => $updated,
        'value'     => $request->get_param( 'email' )
    ), 200 );
}

 

Finally, again in /endpoint/Admin.php, we want to update our REST routes, as well as each route’s respective callback function. Instead of get_example(), update_example() and delete_example(), we want get_contact_email(), update_contact_email() and delete_contact_email().

Click here to see the complete changes for this step on GitHub.

Hopefully all of this has been relatively simple to follow so far. All we’ve really done is update names in order to improve readability and to more logically reflect what our code is doing. If you really wanted to, you could just use WP-Reactivate’s default Example endpoint, option and parameter name etc, but hopefully this little exercise will have given you a better idea of the structure and flow of the PHP side of WP-Reactivate.

Preparing the Settings Page

Having made it this far, you’re probably itching to start getting stuck into the React components. Nearly there, promise – the last thing we need to do on the PHP side is change our settings page name and slug from the default “WP Reactivate” (/options-general.php?page=wp-reactivate) to “WPR Contact Form” (/options-general.php?page=wpr-contact-form).

So open up /includes/Admin.php and update the add_plugin_admin_menu() function on line 145 as follows:

/**
	 * Register the administration menu for this plugin into the WordPress Dashboard menu.
	 *
	 * @since    1.0.0
	 */
	public function add_plugin_admin_menu() {
		/*
		 * Add a settings page for this plugin to the Settings menu.
		 */
		$this->plugin_screen_hook_suffix = add_options_page(
			__( 'WPR Contact Form', $this->plugin_slug ),
			__( 'WPR Contact Form', $this->plugin_slug ),
			'manage_options',
			$this->plugin_slug,
			array( $this, 'display_plugin_admin_page' )
		);
	}

As you can see on line 153, the menu slug is based on our $plugin_slug property defined in our base class Plugin.php, so let’s open up  /includes/Plugin.php and change the $plugin_slug value to ‘wpr-contact-form’:

/**
 * @subpackage Plugin
 */
class Plugin {

	/**
	 * The variable name is used as the text domain when internationalizing strings
	 * of text. Its value should match the Text Domain file header in the main
	 * plugin file.
	 *
	 * @since    1.0.0
	 *
	 * @var      string
	 */
	protected $plugin_slug = 'wpr-contact-form';

Now our settings page has the correct name and menu slug and we can finally start working on the settings page itself.

Click here to see the complete changes for this step on GitHub.

React time!

Updating the Admin container

Open up /app/containers/Admin.jsx. Let’s briefly run through what we’re going to be changing here. From top to bottom, we’re going to be:

Changing our state name from ‘exampleSetting’ to ’email’ (and ‘savedExampleSetting’ to ‘savedEmail’) throughout the component.

constructor(props) { // On init:
    super(props);

    // Set the default states
    this.state = {
      email: '',
      savedEmail: ''
    };

Pointing each of the fetchWP method calls (get, update and delete) to our new admin endpoint.

getSetting = () => {
    this.fetchWP.get( 'admin' )

Changing the key in the data object in our updateSetting() request from ‘exampleSetting’ to ’email’.

updateSetting = () => {
    this.fetchWP.post( 'admin', { email: this.state.email } )

Updating our render() method markup to display an appropriate page title and email input field on our settings page.

render() {
    return (
      <div className="wrap">
        <form>
          <h1>Contact Form Settings</h1>

          <label>
          Notification Email:
            <input
              type="email"
              value={this.state.email}
              onChange={this.updateInput}
            />
          </label>

          <button
            id="save"
            className="button button-primary"
            onClick={this.handleSave}
          >Save</button>

          <button
            id="delete"
            className="button button-primary"
            onClick={this.handleDelete}
          >Delete</button>
        </form>
      </div>
    );
  }

Click here to see the complete changes for this step on GitHub.

Save Admin.jsx and, as long as our ‘yarn build’ task is still running, our JS will be recompiled. You should now be able to see the markup changes on the settings page.

 

Test out saving and deleting a value in the Notification Email field. If you’ve followed the tutorial correctly up to this point, both of these actions should work correctly. However, the only way to see if it’s working at this stage is to refresh the page and check that the value persists – clearly this will not do! What we need is an on-screen notice telling us that the data was saved or deleted correctly, or that an error occurred.

So let’s build a new Notice component to do exactly that.

Building the Notice component

The Admin.jsx file we’ve just been working on can be seen as a React ‘container component’. The same can be said for the Shortcode.jsx and Widget.jsx files in the /app/containers folder. In short, ‘container components’ are concerned with how things work, and it’s generally in these components that all of the logic and data processing takes place.

The notice component we’re going to build can be considered to be a ‘presentational component’, to be imported and called within the Admin.jsx ‘container’. Presentational components are concerned with how things look, and their output is generally based on already-processed data passed down from the parent container.

There are no hard and fast rules here, but I’ve found that it’s definitely advantageous to keep this pattern in mind when developing React apps. As your app starts to expand, you will be grateful to know exactly where to find all of the logic for a particular piece of functionality. At the same time, by keeping all of the data processing and calculations in our container components, our presentational components become infinitely more reusable. It’s well worth reading through Krasimir Tsonev’s concise summary of this ‘Presentational and Container Component’ pattern here.

So let’s create a new folder in /app/ called ‘components’. Now create a new file in /app/components/ called notice.jsx.

Before getting into it, let’s quickly define what this component is going to do. A good place to start when creating a new component is deciding on what props it should receive from the container.

Basically, we want to return a group of elements containing standard WordPress notice markup, so as to take advantage of the existing notice styling in WordPress. The message and type (success, error or warning) of the notice will be based on the specific event that has taken place. So, most importantly, our notice component should receive a ‘notice’ prop: an object containing the specific message and type for a particular notice.

Let’s add a few more props to lend some flexibility to this component:

  • showDismiss: a boolean prop telling our component whether or not to show a dismiss button on the notice.
  • onDismissClick: a function prop, defined in and passed down from the parent container (Admin.jsx), that will run when the dismiss button is clicked.
  • duration: a number prop telling our notice component how long to display before automatically dismissing itself.
import React from 'react';
import PropTypes from 'prop-types';

export default class Notice extends React.Component {
    render() {
        return;
    }
}

Notice.defaultProps = { // Here we set default values for some of our props
    duration: 4000,
    showDismiss: true,
    onDismissClick: null,
};

Notice.propTypes = { // And define our propTypes
    duration: PropTypes.number,
    showDismiss: PropTypes.bool,
    onDismissClick: PropTypes.func,
    notice: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.shape({
            type: PropTypes.string,
            message: PropTypes.string
        })
    ]),
};

When our component mounts, we will check if the duration prop is greater than zero. If it is, we’ll use the window.setTimout() method to automatically run our onDismissClick function prop after the set duration has been reached. If the duration prop is not greater than zero, we will not dismiss the notice automatically. Similarly, when our component unmounts (is dismissed), and the notice was set to dismiss automatically, we will need to reset the timer with the window.clearTimeout() method.

export default class Notice extends React.Component {

  componentDidMount() { // As soon as the component has mounted
    if (this.props.duration > 0) { // if the duration prop is greater than zero
      this.dismissTimeout = window.setTimeout(this.props.onDismissClick, this.props.duration); // then, after the set duration has passed, run the onDismissClick function that has been passed down as a prop from our Admin.jsx container
    } // (otherwise, do nothing until manually dismissed)
  }

  componentWillUnmount() { // When the component is about to removed from the DOM
    if (this.dismissTimeout) { // If this.dismissTimeout was set in componentDidMount()  
      window.clearTimeout(this.dismissTimeout); // reset the timer when the notice is dismissed (and therefore 'unmounted')
    }
  }

We use the React ‘lifecycle methods’ componentDidMount() and componentWillUnmount() to handle the above functionality. It is very important to become familiar with React’s component ‘lifecycle methods’ – several methods that run at different stages of a component’s existence in the DOM. You would have touched on this briefly in the React quickstart guide, but take a look at ‘The Component Lifecycle’ section here for a nice overview of the available lifecycle methods.

Since we’re including the showDismiss prop, we will define a dismiss variable in our render() method that will either be empty or contain the markup for our dismiss button, depending on whether showDismiss is true or false.

render() {
    let dismiss; // define dismiss as an empty variable

    if (this.props.showDismiss) { // if our showDismiss prop is set to true
      dismiss = ( // then set the dismiss variable to contain our dismiss button markup
        <span tabIndex="0" className="notice_dismiss" onClick={ this.props.onDismissClick /* when this element is clicked, fire the onDismissClick function that we've passed down as a prop from Admin.jsx */ } >
          <span className="dashicons dashicons-dismiss"></span>
          <span className="screen-reader-text">Dismiss</span>
        </span>
      );
    }

Finally, we will output the contents of the notice prop object directly into our notice markup: the type will be used to define the class name (notice-success or notice-error, standard WordPress notice classes), while the message will simply be displayed within the notice.

return ( // The returned markup uses the standard WordPress notice classes. The resulting notice class and message will depend on the contents of the 'notice' prop object, passed down from Admin.jsx. 
      <div className={`notice is-dismissible notice-${this.props.notice.type}`}>
        <p><strong>{this.props.notice.message}</strong></p>
        { dismiss }
      </div>
    );

That’s quite a bit to take in, so don’t worry if you’re confused at this point. Click here to see the complete changes for this step on GitHub, and be sure to read the inline comments – it should all become clearer now that you can see the final code.

Ok, we’re nearly there. We’ve built our notice component, but we haven’t yet included and integrated it into our Admin container. Let’s do that now.

Connecting our new component

Open up /app/containers/Admin.jsx again, and import our newly created notice component at the top of the file, after our fetchWP import.

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import fetchWP from '../utils/fetchWP';

import Notice from '../components/notice';

Remember that our Notice component expects a notice object prop and an onDismissNotice function prop to be passed in order to operate.

For the notice prop, we will add another state to our Admin container called notice. In our constructor we set the default notice state value to false.

constructor(props) { // On init:
    super(props);

    // Set the default states
    this.state = {
      email: '',
      savedEmail: '',
      notice: false,
    };

Then, in our processOkResponse() function, we set the notice state as an object, the message and type values of which will depend on the outcome of each respective fetch statement.

processOkResponse = (json, method) => {
    if (json.success) {
      this.setState({
        email: json.value,
        savedEmail: json.value,
        notice: {
          type: 'success',
          message: `Setting ${method} successfully.`,
        }
      });
    } else {
      this.setState({
        notice: {
          type: 'error',
          message: `Setting was not ${method}.`,
        }
      });
    }
  }

We will also send a warning notice in our saveSetting() function if the setting is unchanged from its existing server value.

handleSave = (event) => {
  event.preventDefault();
  if (this.state.savedEmail === this.state.email) {
    this.setState({
      notice: {
        type: 'warning',
        message: 'Setting unchanged.',
      }
    })
  } else {
    this.updateSetting();
  }
}

In the render() method, we will check the notice state and only return a notice if its value is not false.

The onDismissNotice prop expects a function, so we will create a new function in our Admin container called clearNotice(). This function will simply set our notice state back to false, effectively unmounting and dismissing the Notice component.

clearNotice = () => {
    this.setState({
      notice: false,
    });
  }

Now we are able to correctly call our Notice component by sending it the props is requires to function. (The duration and showDismiss props have defaults set in the Notice component itself, so these are optional.)

<Notice
  notice={this.state.notice}
  onDismissClick={this.clearNotice}
/>

Finally, we will declare a notice variable in our render() method, so that we can conditionally mount the notice component only when it is needed.

render() {
    let notice;

    if ( this.state.notice ) {
      notice = <Notice notice={this.state.notice} onDismissClick={this.clearNotice} />
    }

    return (
      <div className="wrap">
        {notice}

Now, when we save or delete our setting, or an error occurs, a dismissible admin notice will display. You will notice that the styling isn’t quite right – the dismiss button displays below the message rather than next to it. We need to add the following short CSS snippet to /css/admin.css in order to fix this.

.notice {
  position: relative;
}
  
.notice_dismiss .dashicons {
  position: absolute;
  top: 10px;
  right: 9px;
}

And now our notices will display correctly:

Click here to see the completed Admin container, which now includes our Notice component.

And that’s as far as we’ll go in this part of the tutorial. Congratulations, you’ve covered a lot here. Don’t worry if you feel like you have more questions than answers at this stage, there’s no denying that React comes with a steep learning curve when you’re just getting started. In our next tutorial we will focus on building the contact form itself, connecting it up with what we’ve built today, and essentially completing our simple React contact form plugin.

In the meanwhile, I encourage you to tinker, build things, (inevitably) break things and get stuck into the React docs as much as possible. Whilst by no means do I consider myself to be an authority on React – there are still (often) times that I have to return to the docs – one thing I do know is that there’s no better method of learning than digging in and getting your hands dirty. Hopefully this tutorial has given you the ability to do just that.

You can find all of the code for this part of the tutorial on GitHub.

 

Leave a Reply

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