How I Built It

How I built it: A WordPress plugin to calculate user meeting times that uses the WP REST API and React.js

This post is based on a talk that I gave at the East Bay WordPress Meetup on December 10, 2017. The complete code for the plugin can be found on GitHub.

The problem

I work with a team of six that is distributed across the globe. In order to work effectively as a team, we hold regular meetings over video. Shortly after I joined the team, I noticed that there was sometimes confusion over when the meetings would begin. With team members in six different timezones, the confusion was understandable.

Our team lives in six different timezones across the globe. (Image adapted from Free Vectors by www.Vecteezy.com)

The initial solution

Initially, I wrote a simple Python app on Google App Engine, using the pytz library to calculate user meeting times. The app listed each team member and their timezone. To calculate meeting times, a user would enter the meeting time and date and select a reference timezone. The app would then generate a list of team members and their personalized meeting times, which we could paste into a task in our project management system.

The Google App Engine solution served its purpose, basically eliminating any confusion about when our team meetings would start. However, it was somewhat hard to find and hard to use. For example, if a team member moved to a new timezone, they would have to look up the correct name of their new timezone in this database. If they didn't enter it correctly, it would throw an error. I could have spent time improving the interface, but I was focusing on improving my skills as a WordPress developer, and it worked well enough.

An opportunity to play with shiny, new things

With the introduction of the WP REST API and WordPress's adoption of React.js as the JavaScript framework behind the Gutenberg editor, I was looking for an excuse to build something with these new tools. A meeting timezone calculator seemed like a good first project. Additionally, building it as a WordPress plugin meant that it would be easier for our team to find – because it could be installed on our professional development blog, a site that we are already logged into on a daily basis.

How I built it: The WordPress part

The WordPress part of the plugin is quite simple. First, it creates a custom user meta field (called user_timezone) that can be set via a User Timezone dropdown field when editing a user's profile in the WordPress admin. The timezone options are borrowed from the "Timezone" dropdown menu that appears under Settings > General in a regular WordPress install.

The plugin creates a custom user meta field for the user's timezone.

In order for user timezones to be accessible via the REST API, I had to add them to the users WP API endpoint. During my testing, I discovered that only users who have published content are included in the users WP API endpoint by default. Since I wanted all registered users to be available to my plugin, regardless of whether they had published content, I first added a filter to remove 'has_published_posts' from the WP API user query.

// Includes all users in API result even if they haven't published posts
add_filter( 'rest_user_query', 'red_remove_has_published_posts_from_wp_api_user_query', 10, 2 );
/**
 * Removes `has_published_posts` from the query args so even users who have not
 * published content are returned by the request.
 *
 * @see https://developer.wordpress.org/reference/classes/wp_user_query/
 *
 * @param array           $prepared_args Array of arguments for WP_User_Query.
 * @param WP_REST_Request $request       The current request.
 *
 * @return array
 */
function red_remove_has_published_posts_from_wp_api_user_query( $prepared_args, $request ) {
    unset( $prepared_args['has_published_posts'] );
    return $prepared_args;
}

Then I made the user_timezone user meta value available to rest API by modifying the default response. I found it helpful to use Postman to confirm that I had successfully modified the default WP API users endpoint. The URL where you would find data for the users endpoint is: https://wpsiteurl.com/wp-json/wp/v2/users.

// Make user_timezone user meta value available to rest API
add_action( 'rest_api_init', 'create_api_user_meta_field' );
 
function create_api_user_meta_field() {
    register_rest_field( 'user', 'user_timezone', array(
            'get_callback'    => 'get_user_meta_for_api',
            'schema'          => null,
        )
    );
}
 
function get_user_meta_for_api( $object ) {
    //get the id of the user object array
    $user_id = $object['id'];
 
    //return the user meta
    return get_user_meta( $user_id, 'user_timezone', 1 );
}
Users API output for Jamie, including user_timezone. Viewed using Postman.

Finally, I created a page under Admin > Tools called "Meeting Timezones" to display the interface where users can calculate meeting times. The PHP part of the plugin simply inserts an empty div with id="mtg-tz-container and then enqueues the React-generated styles and scripts.

A new page created under Tools called "Meeting Timezones."

How I built it: The React part

The React part is where the magic happens, transforming the empty div shown above into this:

The user interface for the meeting timezone calculator, generated by React.

The beauty of a front-end JavaScript library, like React.js, is that it enables you to have an app that responds to user input without having to reload the page. That's a huge advantage for a tool that runs in the WordPress admin dashboard, where page loads are notoriously slow.

React is a modular framework, and each component can handle two types of data: props and state. Props are values that don't change, while the state contains values that can be updated. In my meeting timezone calculator app, there is an outer component called <MtgTzContainer />, and it makes a call to the WP REST API to retrieve the user data, including user timezones. It stores that data in its state and then renders two components: <UserTimezoneList /> and <MeetingTimeForm />. It passes the user data to those components as props.

The <UserTimezoneList /> component renders the top section of the user interface that shows each user's name, avatar, and timezone. I also included a checkbox next to each user to indicate whether that individual would be included in the final meeting times list.

The <MeetingTimeForm /> component provides a datepicker and timezone dropdown menu. The datepicker is a component borrowed from an external library called react-datepicker, while the timezone dropdown menu is simply a select element whose options are a list of the unique timezones represented by the list of users. This component is where a lot of the work happens. It relies on the libraries moment.js and moment-timezone.js to calculate meeting times in various timezones. Finally, it passes the results to the <UserMeetingTimes /> component, which displays the output.

While building the React app and WordPress plugin, I ran into a few interesting issues:

  • The names of the .js files that React generates are modified with cache-breaking hashes. For example, main.js might become main.a8429063.js. Since WordPress requires you to enqueue scripts by name, and since that name will change every time the file is generated, I used the strategy explained here to look for and enqueue any file in the targeted folder that ends in .js.
  • I found that I needed to enqueue the script generated by React late so that the empty div that React looks for exists when the script is loaded.
  • While working on the React app locally (and before integrating it into the WordPress plugin), I needed to provide it some test user data from a WordPress API. I accomplished that by using a hardcoded URL to my personal site, where I had set up some dummy user data. However, when the app is integrated into a WordPress plugin, I want it to look for the users WP API endpoint for the site on which it's installed. To account for both situations, my script first looks at the current URL to see whether it contains /wp-admin. If so, it loads data from the current site's WP API. Otherwise, it catches the error and loads data from the dummy API. 

And you can, too!

This was my first foray into React.js. I got started with some free tutorials on YouTube. Fortunately, there are so many learning resources for React these days that you can easily find one that fits your learning style. I was also lucky to have create-react-app at my disposal, which made it very easy to get up and running with a React app on my local machine.

Figuring out how to write a WordPress plugin that uses React was a bit of a challenge. However, I found this post that walks you through a very simple way to get a WordPress plugin up and running with React. Instead of using a package manager and doing things the "right" way, it shows you how to load React and its dependencies from external URLs.

The author of that post acknowledges that it's not the "correct" way to do it. However, that simple approach helped me get started and, from there, I was able to figure out how to build my React app in the proper way, using node package manager and generating the final script that I can then package with the plugin. A future improvement would be to write a script that packages the React-generated scripts with the final WordPress plugin automatically. Additionally, I'd like to learn more about Redux, which is a tool for organizing state within an app.

The completed project can be viewed on GitHub.