Building a WordPress plugin with React – Part 2

Please note that we have updated WP Reactivate and made some changes to the first tutorial since it was originally published. If you previously completed the first tutorial, you may want to go back and run through it again before continuing.

Welcome to the second and final part of this tutorial series on building a React powered WordPress plugin with the WP Reactivate boilerplate. If you haven’t already done so, be sure to complete the first tutorial before continuing. We have, once again, made the code for this tutorial available on GitHub, so check out the link to the relevant commit after each step.

Overview

In the first tutorial, we setup our new Admin REST route, built the settings page, and created a Notice React component.

We will now continue where we left off and create the frontend of our plugin. Specifically, we will need to:

  • Update the Shortcode and Widget PHP classes to appropriately reflect our contact form plugin
  • Create a new Submission endpoint with which to handle our contact submission.
  • Build a Contact Form React component to be included in WP-Reactivate’s Widget and Shortcode React containers.

Use this commit (which includes everything we did in the first tutorial) as a starting point before moving on.

The PHP

Updating the Shortcode and Widget classes

In this quick step we will simply change the shortcode in includes/Shortcode.php from the default [wp-reactivate] to [wpr-contact-form], and set the widget title and description in includes/Widget.php to something more appropriate for our new plugin.

   /**
	 * Initialize the plugin by setting localization and loading public scripts
	 * and styles.
	 *
	 * @since     1.0.0
	 */
	private function __construct() {
		$plugin = Plugin::get_instance();
		$this->plugin_slug = $plugin->get_plugin_slug();
		$this->version = $plugin->get_plugin_version();

		add_shortcode( 'wpr-contact-form', array( $this, 'shortcode' ) ); // Change the shortcode tag to 'wpr-contact-form'
	}

includes/Shortcode.php

class Widget extends \WP_Widget {

	/**
	 * Initialize the widget
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		$plugin = Plugin::get_instance();
		$this->plugin_slug = $plugin->get_plugin_slug();
		$this->version = $plugin->get_plugin_version();

		$widget_ops = array(
			'description' => esc_html__( 'WP Reactivate contact form widget.', $this->plugin_slug ), // Update the widget description
		);

		parent::__construct( 'wpr-widget', esc_html__( 'WPR Contact Form', $this->plugin_slug ), $widget_ops ); // and the widget name
	}

includes/Widget.php

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

Creating the Submission endpoint

We need to create a new REST endpoint in order to receive and handle the data sent from our contact form. To speed this up, let’s just duplicate the /endpoint/Admin.php file to a new Submission.php file in the /endpoint/ folder. We can then just rename and remove where necessary.

Firstly, in our new Submission.php file, let’s change the class name to Submission.

<?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 Submission { // Name our new endpoint class

The REST Route

Next, let’s setup the REST route. That’s right, we’ll only need a single CREATABLE route here, so we can remove the READABLE, EDITABLE and DELETABLE routes that were copied over from our Admin endpoint.

We’ll need to change our $endpoint variable to ‘/submission/’, and set the callback to our new callback function (which we’ll create in the next step). Finally we’ll add some arguments to this route to tell it how to handle each parameter and to take advantage of the WP REST API’s built-in field validation.

    public function register_routes() {
        $version = '1';
        $namespace = $this->plugin_slug . '/v' . $version;
        $endpoint = '/submission/'; // set our new endpoint

        register_rest_route( $namespace, $endpoint, array(
            array(
                'methods'   => \WP_REST_Server::CREATABLE,
                'callback'  => array( $this, 'process_submission' ), // set to our new callback function
                'args'      => array( // the expected parameters for this route
                        'name' => array(
                            'required' => true, // means that this parameter must be passed (whatever its value) in order for the request to succeed
                            'type' => 'string',
                            'description' => 'The user\'s name',
                            'validate_callback' => function( $param, $request, $key ) { return ! empty( $param ); } // prevent submission of empty field
                        ),
                        'email' => array(
                            'required' => true,
                            'type' => 'string',
                            'description' => 'The user\'s email address',
                            'format' => 'email' // we set the format in order to take advantage of built-in email field validation
                        ),
                        'message' => array(
                            'required' => true,
                            'type' => 'string',
                            'description' => 'The user\'s message',
                            'validate_callback' => function( $param, $request, $key ) { return ! empty( $param ); }
                        ),
                    ),
                )
            )
        );
    }

The Callback function

Since we’re only dealing with one route, we will only need one callback function. Go ahead and remove all of the callback functions and the unnecessary admin_permissions_check() function that we copied over from our Admin endpoint. We will add a very basic callback function from scratch.

The callback function is where we receive and process the request sent via the associated route. In this case our $request object will contain all of the data sent from our contact form via the ‘/submission/’ route. Let’s start by extracting the parameters that we want to process: the name, email and message fields submitted from our contact form.

public function process_submission( $request ) {
        
        $name = $request->get_param( 'name' );
        $email = $request->get_param( 'email' );
        $message = $request->get_param( 'message' );
        
}

With the submitted data now available in our PHP, we have myriad options as to how to process and utilise it. One example would be store each submission as a custom post – perhaps we could assign the name to the post title, the message to the post content and the email address to a custom field. We could quite easily achieve this with the wp_insert_post() and update_post_meta() WordPress functions.

Another example, and in fact the functionality I originally had in mind for this tutorial, would be to send an email to the notification email address that we saved in our new settings panel. Again, this would be easy to achieve with the wp_mail() WordPress function. I eventually decided against this approach due to the difficulty of testing emails on a local environment – a can of worms that would add very little if any value to this tutorial.

I could go on, but hopefully you get the idea – the WP REST API provides a reliable bridge between our React components and our WordPress installation. Now let’s dial the excitement down a few notches while I describe what we’re going to do in our callback function: for the purposes of this tutorial we’re simply going to return the extracted parameters to our contact form component via the WP_REST_Response, and log them in our browser’s JavaScript console. This is simply to demonstrate that the React component and PHP callback function are communicating.

    public function process_submission( $request ) {
        
        $name = $request->get_param( 'name' );
        $email = $request->get_param( 'email' );
        $message = $request->get_param( 'message' );

        return new \WP_REST_Response( array(
            'success'   => true,
            'value'     => $message . ' - ' . $name . ' (' . $email . ')'
        ), 200 );
       
    }

There’s one last thing that I feel is worth mentioning before we move on. If you’re using Google Chrome as your browser, this is an ideal situation in which to make use of the WP PHP Console plugin (which requires the PHP Console browser extension). It allows you to log PHP variables in Chrome’s JavaScript console (as well as in OS alerts) – basically console.log for PHP. If you’re interested, I wrote an article on getting setup with the plugin here. If you wanted to go with this approach, you could replace the \WP_REST_Response() statement in the above example with a \PC::debug() statement, allowing you to log the parameters directly from the PHP callback function.

        if ( class_exists('PC') ) {
            \PC::debug( $message . ' - ' . $name . ' (' . $email . ')', 'Contact Submission' );
        }

Connecting the endpoint

Finally, we will need to include our new endpoint in the base plugin file. Let’s add a new variable for our Submission endpoint called $wpr_rest_submission.

/**
 * Initialize Plugin
 *
 * @since 1.0.0
 */
function init() {
	$wpr = Plugin::get_instance();
	$wpr_shortcode = Shortcode::get_instance();
	$wpr_admin = Admin::get_instance();
    $wpr_rest_admin = Endpoint\Admin::get_instance();
    $wpr_rest_submission = Endpoint\Submission::get_instance(); // connect our new endpoint
}
add_action( 'plugins_loaded', 'Pangolin\\WPR\\init' );

 

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

React time!

Now that our new endpoint is in place, we can move onto building the Contact Form component itself. Remember to run ‘yarn build’ to automatically compile your React changes as you work.

Setting up the Contact Form component

Create a new file in the /app/components/ folder called contactForm.jsx. Let’s start by laying down some basic form markup in our new React component.

import React from 'react';

export default class ContactForm extends React.Component {
 
  render() {    
    return (
      <div>
        <form>
          <label>
          Name:
            <input
              type="text"
              name="name"
            />
          </label>

          <label>
          Email:
            <input
              type="email"
              name="email"
            />
          </label>

          <label>
          Message:
            <textarea 
              name="message"
            />
          </label>

          <button
            id="submit"
            className="button button-primary"
          >Submit</button>

        </form>;
      </div>
    );
  }
}

 

We need to import and call our new component in our existing Shortcode and Widget React containers (app/containers/Shortcode.jsx and app/containers/Widget.jsx). While we’re here, we will also make some minor adjustments to the jsx markup in these containers to match our contact form use-case.

import React, { Component } from 'react';
import PropTypes from 'prop-types';

import ContactForm from '../components/contactForm';

export default class Widget extends Component {
  render() {
    return (
      <div>
        <section className="widget">
          <h1 className="widget-title">{this.props.wpObject.title}</h1>
          <ContactForm wpObject={this.props.wpObject} />
        </section>
      </div>
    );  
  }
}

Widget.propTypes = {
  wpObject: PropTypes.object
};

app/containers/Widget.jsx

import React, { Component } from 'react';
import PropTypes from 'prop-types';

import ContactForm from '../components/contactForm';

export default class Shortcode extends Component {
  render() {
    return (
      <div>
        <h1>{this.props.wpObject.title}</h1>
        <ContactForm wpObject={this.props.wpObject} />
      </div>
    );
  }
}

Shortcode.propTypes = {
  wpObject: PropTypes.object
};

app/containers/Shortcode.jsx

Now add the widget or shortcode to a page and you should see our new form.

Great, that’s a form alright*. However, in its current state, it’s utterly useless. Let’s go back to our new Contact Form component (app/components/contactForm.jsx) and make it functional.

*If what you see does not resemble a form, curse your theme author and switch to one of the default WordPress themes for the rest of this tutorial.

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

Making it functional

We’ll need to import the PropTypes module as well as our FetchWP utility to handle the REST API request. The only prop we’ll need here is the wpObject, which contains the REST URL and REST Nonce passed from our Widget and Shortcode PHP classes.

In our constructor we will set a default state for each of the form fields – name, email and message. We will also add a submitted state which will be set to true only after the form has been successfully submitted. This will allow us to conditionally hide the form and return a message once it’s been submitted. And lastly, we will add an error state, used to store any errors returned from our REST request.

import React from 'react';
import PropTypes from 'prop-types';

import fetchWP from '../utils/fetchWP';

export default class ContactForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      error: false,
      submitted: false,
      name: '',
      email: '',
      message: '',
    };

    this.fetchWP = new fetchWP({
      restURL: this.props.wpObject.api_url,
      restNonce: this.props.wpObject.api_nonce,
    });
  }

  render() {
    return (
      ...
    );
  }
}

ContactForm.propTypes = {
  wpObject: PropTypes.object
};

 

Next, let’s add a function to handle changes to our form fields. As each field is updated (typed into) the respective state should be updated as well. The following function will dynamically set the state of each field based on its ‘name’ attribute – so ensure that the ‘name’ attribute is identical to the respective state name for each field. You can read more about this approach to handling multiple inputs with a single function here and here.

handleInputChange = (event) => {
    const target = event.target;
    const value = target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

 

Now we just need to call this function in the onChange attribute of each field, eg:

<form>
  <label>
    Name:
    <input
      type="text"
      name="name"
      onChange={this.handleInputChange}
    />
    </label>
    ...

 

Now that our field states are being set correctly, we can move onto handling the submission of our form data. Let’s create a new handleSubmit function, and use our fetchWP utility to post the name, email and message parameters to the ‘submission’ endpoint.

If the request is successful, we will set the submitted state to true and reset the error state to false (thus clearing any previously set error message). We will also add a console.log statement here to demonstrate that our data is successfully reaching and being returned from our PHP callback function.

If the request is not successful we will update the error state to the error message returned by the REST API.

handleSubmit = (event) => {
    event.preventDefault();

    this.fetchWP.post( 'submission', {
      name: this.state.name, // send our name, email and message states to the 'submission' endpoint
      email: this.state.email,
      message: this.state.message, 
    } )
    .then(
      (json) => {
        this.setState({ // if successful
          submitted: true, // set the submitted state to true
          error: false // and clear any previous error message
        }),
        console.log(`New Contact Submission: ${json.value}`) // For tutorial purposes. Shows that our data is successfully reaching and being returned from our PHP callback function.
      },
      (err) => this.setState({ // else
        error: err.message // set the returned error message as the error state
      }),
    );
  }

 

We can now call this function in the onSubmit attribute of the form itself, as well as in the onClick attribute of our submit button.

<form onSubmit={this.handleSubmit}>
  ...
  <button
    className="button button-primary"
    onClick={this.handleSubmit}
  >Submit</button>

 

Since our conditional states are now being set correctly, we can return to our render() method and move our markup into variables. We will use the submitted state to either display the contact form, or a thank you message. Similarly, our error message markup will only be included when the error state is not false.

render() {
    const contactForm = this.state.submitted ? <p>Thanks for getting in touch!</p> : // if our form has been submitted, display a thank you message instead
      <form onSubmit={this.handleSubmit}>
        <label>
        Name:
          <input
            type="text"
            name="name"
            onChange={this.handleInputChange}
          />
        </label>
        ...
      </form>;

    const error = this.state.error ? <p className="error">{this.state.error}</p> : ''; // if there is an error, display it
      
    return (
      <div>
        {contactForm}
        {error}
      </div>
    );
  }

 

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

Testing our form component

Firstly, try submitting the form with all of the fields empty. An error message should display below the form: “Invalid parameter(s): name, email, message”.

When we setup our Contact REST route, we added an anonymous function to the ‘validate_callback’ attribute of each of our $args. That function simply returns false (i.e., not valid) when the respective field is empty. So without adding any custom error handling, the WP REST API is able to tell us which fields are invalid based on the validate_callback ‘rule’ that we initially defined.

Next let’s try submitting the form with all fields populated, but with an invalid email address. This time you should see: “Invalid parameter(s): email”.

Since we included ‘format’ => ’email’ when setting up the ’email’ arg in the REST route, the WP REST API expects the value of this field to be a valid email address. Because this is not the case, it throws an error.

Error messages for empty fields and invalid email address

Finally, let’s submit a valid entry, with all fields populated and using a valid email address.

 

 

Success! We have submitted a valid entry, allowing the POST request to complete successfully, thus setting our submitted state to true, and replacing the form with a thank you message. You should also see the submitted data, which has reached and been returned from our Submission endpoint callback function, logged in your browser console.

Conclusion

Congratulations, you have built a functional React WordPress plugin! In the process you have learned how to develop and display React applications on both the front and backend of your website using the WP Reactivate boilerplate. Although this is certainly a simple example, it covers the foundational basics required to build just about any WP Reactivate based React-WordPress application. Perhaps the most important takeaway here is understanding how the REST API allows data to flow between your React components and your WordPress site. I hope you have found some value in these tutorials and wish you success in all the great things you’re going to build with the WP Reactivate boilerplate!

Along with Gutenberg, WordPress 5.0 will introduce a host of WordPress focused Javascript packages which can be used in the development of plugins like these. We’re planning WP-Reactivate v2 which will make use of these packages as well as support Gutenberg block creation, so stay tuned!

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

Leave a Reply

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