At Human Made, we're constantly trying to push the boundaries of what's possible with WordPress. We build internal projects to test out the latest and greatest technologies and tools, then use what we've learnt to build out new and better experiences for our clients.
We've been big proponents of React since we started experimenting with it internally a few years ago. Many of our internal tools are built with React, including our server management interface, cross-company communication blogs, and other tiny, single-purpose utilities including our tool to help employees book time off. We've also been using it increasingly on client projects, using to build internal tools to help editorial teams, as well as going completely headless.
From working with React internally and on client projects, we've been able to see the common pain points when developing. We found ourselves often writing the same boilerplate and connectors repeatedly, as well as constantly struggling with tooling. We've been working on solving these problems internally, and we wanted to share our solutions to these common problems.
We're officially releasing three tools today: react-wp-scripts for development tooling, Repress for smart Redux stores, and react-oembed-container to simplify oEmbed rendering. Each of these tools is built for a need we've had, and they can be used together or as standalone projects.
Easy Development with react-wp-scripts
If you've worked with React for a while, you'll remember how painful setting up a project used to be. Thankfully, create-react-app
(CRA) came along and revolutionised this by making it as easy as running a single command. We wanted to do the same for WordPress-based projects, and make it super easy to build amazing apps.
react-wp-scripts
is our tool for handling this. It extends create-react-app
(and the underlying tool, react-scripts
) with WordPress-specific helpers. This allows all the features of CRA to be used inside a WordPress project, including live reloading (and hot-loading), error reporting, and easy switching between development and production builds.
Installation
Starting a new project with react-wp-scripts is super easy. Simply run:
npx create-react-app --scripts-version react-wp-scripts your-directory/
(Don't have npx
? Upgrade your copy of node, or follow the manual installation instructions instead.)
You can also easily add react-wp-scripts
to your existing project; simply follow the installation instructions in the project's documentation.
You'll need to also add the PHP to load in your scripts. The bootstrap command will copy the loader file to your project for you, so all you have to do is hook it in to WordPress:
require __DIR__ . '/react-wp-scripts.php';
add_action( 'wp_enqueue_scripts', function () {
// In a theme, pass in the stylesheet directory:
\ReactWPScripts\enqueue_assets( get_stylesheet_directory() );
// In a plugin, pass the plugin dir path:
\ReactWPScripts\enqueue_assets( plugin_dir_path( __FILE__ ) );
} );
Once your project is set up with react-wp-scripts
, you can simply run npm start
to use the development, live-/hot-reloading React app. When you're ready to build your project, use npm run build
just like a regular CRA project; the PHP loader will automatically use the built version whenever you aren't running the development builder.
Help Us Out
react-wp-scripts
is still early days, although we're beginning to use it on all our new projects. We want your help to make it easier to use, and get to full feature parity with regular CRA apps. There are still a few broken features (such as jumping directly to your editor) that we'd love to have.
We're also going to add standalone commands so that starting a new project will be as easy as:
npx create-react-wp-plugin my-plugin
npx create-react-wp-theme my-theme
We need your help testing and developing this to make it into the best tool for the modern WordPress ecosystem. Help us out!
Power-Up your Redux Store with Repress
When building out React apps, we found ourselves repeatedly writing the same boilerplate code to get data from WordPress. This involved sending off requests to the REST API, storing that data somewhere, and pulling it back out to render. Doing this repeatedly was a pain, and making sure we didn't miss anything was tough.
To make this easier, we created Repress, a Redux library for the WordPress REST API. Repress natively understands the WordPress REST API, and makes caching super simple. Unlike many other Redux libraries for WordPress, Repress can be dropped into an existing store, allowing you to progressively adopt it. You can even combine Repress with other methods of retrieving API data in the same store if you'd like.
Repress is built around two fundamental pieces: API resources (like a post), and queries (called "archives" in Repress). Internally, Repress shares these resources between queries, allowing efficient reuse of resources, just like WordPress' object cache.
Installation
Repress is published as an npm package, so you can add it just like any other package:
npm install --save @humanmade/repress
(You'll need to already have a Redux store set up, as well as Redux Thunk and React.)
Once added, you need to establish your type instances. Usually, you'll have a types.js
file that handles this in a central place:
// types.js
import { handler } from '@humanmade/repress';
export const posts = new handler( {
type: 'posts',
url: window.wpApiSettings.url + 'wp/v2/posts',
nonce: window.wpApiSettings.nonce,
} );
You then just need to connect this to your reducer wherever you'd like it to live in your store:
// reducer.js
import { combineReducers } from 'redux';
import { posts } from './types';
export default combineReducers( {
// Any regular reducers you have go in here just like normal.
// Then, create a "substate" for your handlers.
posts: posts.reducer,
} );
Using the data is super simple, as Repress provides higher-order components (HOC), including one called withSingle
. This works just like Redux's connect
HOC, and provides props to your component:
// SinglePost.js
import { withSingle } from '@humanmade/repress';
import React from 'react';
import { posts } from './types';
const SinglePost = props => <article>
<h1>{ props.post.title.rendered }</h1>
<div
dangerouslySetInnerHtml={ { __html: props.post.content.rendered } }
/>
</article>;
export default withSingle(
// Pass the handler:
posts,
// And a getSubstate() function so Repress can find the data:
state => state.posts,
// And a mapPropsToId() function so Repress knows what post to get:
props => props.id
)( SinglePost );
Archives
In order to facilitate predictability and cachability, Repress introduces a concept called "archives". Archives act as a filtered view into the list of resources, which allows you to easily reuse resources; for example, if you go from the homepage to a post, this can reuse the existing data for instant rendering.
Archives have to be registered with an ID before use, which allows Repress to cache the result and simplify pagination. They can either be static or dynamic:
posts.registerArchive( 'stickied', { sticky: '1' } );
posts.registerArchive( 'today', () => {
return {
after: moment().startOf( 'day' ).toISOString(),
before: moment().endOf( 'day' ).toISOString(),
}
} );
Using archives is super simple, as Repress provides another HOC called withArchive
, which works just like withSingle
:
// TodayArchive.js
import withArchive from '@humanmade/repress';
import React from 'react';
import { posts } from './types';
const TodayArchive = props => <ul>
{ props.posts.map( post =>
<li key={ post.id }>
{ post.title.rendered }
</li>
) }
</ul>;
export default withArchive(
// Handler object:
posts,
// getSubstate() - returns the substate
state => state.posts,
// Archive ID
'today'
)( TodayArchive );
withArchive
also provides helper props for pagination, loading, and more. This allows you to forget about the process and just worry about making fantastic apps.
Try it Out
We're already using Repress in our internal projects, but we haven't begun using it in production client projects just yet. We want your help to make Repress into a solid library anyone can use. Try it out on your projects today, read the documentation, and let us know what we could improve!
Simple Embedding with react-oembed-container
The web is all about interactive, multimedia-rich experiences. WordPress includes powerful tools to enable using media from across the web using the open oEmbed protocol. This allows writers and editors to add a URL to their post and have the HTML generated by WordPress.
While most embeds generate pretty vanilla HTML, some of the more complex embeds require JavaScript for full interactivity, including Twitter:
For React-powered frontends, you typically receive the post content as a single HTML string. This requires you to use one of React's escape hatches, dangerouslySetInnerHtml
. This is a safe operation (as WordPress has already sanitised the content), but suffers from the limitations of innerHTML
, which importantly includes not adding <script>
elements to the DOM. There are other solutions to this problem, including preparsing the HTML into structured data on the server (Scott Taylor of the New York Times has a great post on this), but these don't automatically solve this problem either.
To solve these problems, we created react-oembed-container, a component which handles all of the complexity for you. This component is directly derived from what we've learnt building out React-powered sites, and is battle-tested. Plus, it works on any HTML and doesn't require WordPress.
Install it right now from npm:
npm install react-oembed-container
Using the container is super simple: simply wrap your normal rendering code with the container:
import EmbedContainer from 'react-oembed-container';
const MyPost = props => {
return <EmbedContainer
markup={ post.content.rendered }
>
{/* for example, */}
<article id={`post-${post.id}`}>
<h2>{ post.title.rendered }</h2>
<div dangerouslySetInnerHTML={{ __html: post.content.rendered }} />
</article>
</EmbedContainer>;
}
react-oembed-container
supports all oEmbed scripts, but contains special support for Facebook, Instagram, and Twitter embeds to improve the embedding process. If you hit into any other special cases, we'd be happy to add support for those too.
While react-oembed-container
is production-ready and in use on high-traffic sites already, we'd love feedback and contribution. If you can think of improvements, let us know!
And Even More!
Apart from these projects we're announcing today, we have a few projects we've already released, plus more in the pipeline. Look out for more information on these in the coming weeks and months!
Gutenberg Blocks with hm-gutenberg-tools
As Matt announced recently, we switched humanmade.com to use Gutenberg. Along the way, we created some reusable tools and blocks, which we packaged up into hm-gutenberg-tools. This includes a Post Select button, more sidebar controls, and a handy wrapper for editable HTML.
Read more about our use of Gutenberg on the original post.
Restsplain: Document Your WordPress REST API
Building frontend-heavy applications with React often involves a lot of work with the WordPress REST API. While the REST API is designed to be self-documenting, the documentation is only available in machine-readable format, which isn't the greatest for humans like me and you. The human-readable documentation is great, but doesn't cover custom APIs that we've built ourselves.
To better visualise and understand the REST API, we built Restsplain, a documentation interface for the REST API. We'll be posting more about Restsplain in the upcoming weeks, but if you can't wait, you can get it from GitHub right now.
Coming Soon: Server-Side React Toolkit
React is a great tool for the frontend, but at the end of the day, WordPress is still fundamentally a server-based project. We're working on tools to make server-side rendering easier, as well as helpers to make preloading data into React easier. These are still a little too experimental to officially release, but you might find hints of them on our GitHub profile.
We Love Feedback!
We'd love to hear if you use any of these tools in your projects. While we've built them to satisfy our own needs, we want them to be ecosystem-wide tools and libraries that can help everyone move faster and create amazing things. Feel free to leave feedback in GitHub issues, or tweet us with your thoughts!