The new WordPress block-based editor Gutenberg is coming to the WordPress soon. While no one has yet defined where Gutenberg will next be used, its been well architected for reuse, which is great, because plugin developers can now use these components in other interfaces, in the WordPress admin and beyond.
Because the Gutenberg team is currently moving a lot of the code that is most likely to be reused into npm modules, this makes it very simple to reuse the Gutenberg components in a React app, even if it isn’t in WordPress. That’s not the only way to import Gutenberg components or utilities to your project if it is in WordPress, which I will discuss in this post.
This post will show how to share React components between Gutenberg blocks, non-Gutenberg wp-admin screens powered by React, and React apps. This is based on work I am currently doing to make my plugin Caldera Forms Gutenberg friendly and rewrite a lot of the user interface in React.
Bringing Gutenberg Modules Into Scope
Gutenberg’s code base is broken up into various modules, for example, “data” for state management or “components” for the UI components the editor is constructed from. This pattern helps navigate the code base since each module is a top-level directory. It also helps tell us how to import a module or component. For example, if we want to use the SelectControl component, we access it via the components module — wp.components.SelectControl.
Let’s look at three options for accessing these modules. The first does not require webpack. The other two do require webpack or to be adapted to some other build system.
Using The wp Global
All of Gutenberg’s libraries are accessible through the global-scoped variable “wp”. That means that the simplest way to bring components into scope is accessing the wp global. This works just fine if assets are managed using wp_enqueue_script and you set the right dependency is set for your script.
In this example, WordPress’s render function, create element is accessed via wp:
That’s it. Works in a block’s JS file or anywhere else in WordPress if you set “element” as a dependency when enquiring your script.
Using wp As A webpack External
If you look at Gutenberg’s source, and you should, it’s a great read, you will see webpack imports like this
This is actually the same thing as accessing the wp.element global. Gutenberg sets up a webpack external for each of the entry points and packages. This is a good pattern that serves traditional WordPress and the more modern webpack well.
You can set up a similar webpack to act as an alias in your plugin. I built a block plugin for alert messages as example code for my WordCamp talks. The webpack config has externals for several Gutenberg packages setup.
Then you can import element module with the same syntax Gutenberg and the Gutenberg documentation use.
Using npm
As I said earlier in this post, the modules of Gutenberg are or will be installable via the JavaScript dependency management system npm. If you are installing a WordPress package ina WordPress plugin, you probably should install it as a development dependency. That way you can use webpack imports, have the module work in your tests, but not add the module to your production build. In WordPress, the dependency is loaded using wp_enqueue_scripts. If you are not developing for the WordPress environment, reverse that advice.
For example, to install WordPress’ element module in a WordPress plugin:
Or to install for use outside of the WordPress environment:
Then to bring the dependency into scope, import it with webpack:
That’s the same as the last few examples. That’s the point really. That line of code works in any context — Gutenberg blocks, other wp-admin screens, apps deployed outside of the WordPress environment and with a little more care, tests.
Managing The wp Global
WordPress uses global state. That makes things complicated, but Gutenberg’s use of the wp global variable is the most manageable global state we’ve ever had as WordPress devs. Let’s look at some gotchas I’ve run into because of the unpredictability of global state and how I fixed these issues.
For Non-Gutenberg wp-admin Screens
In a WordPress plugin, using the WordPress Babel preset makes a lot of sense to me. It keeps the Babel config pretty simple:
{ "presets": [ "@wordpress/default" ] }
One thing that this does is use WordPress’ element to compile JSX. That’s good, as long as the global variable wp.element is set. It is in Gutenberg screens. This can be an issue if your components import React.
I ran into this problem when using React components for a wp-admin screen that shares components with my Gutenberg block. The solution, at the time @wordpress/element was not on npm, was to do what Gutenberg does – define wp.element equal to React.element
This isn’t a scalable solution, but it works. I will refactor this code to use @wordpress/element as I described above. But, this is likely going to be an issue for anyone maintaining React and WordPress code together.
In Tests
One great reason to use React is Jest. I love Jest. Jest is the easiest testing tool I’ve ever used. This isn’t a tutorial on Jest, but I do want to cover setting up tests for components shared between Gutenberg and other React apps.
Because we use Jest in Caldera Forms, we need to make sure that wp.element is defined. Currently we are using a shim, copied from Gutenberg, to prevent errors in our tests. Here it is:
global.wp = { shortcode: { }, apiRequest: { } }; Object.defineProperty( global.wp, 'element', { get: () => require( 'React' ), } );
Looks familiar right? It’s the same thing WordPress does. In fact, there are modules on npm published recently to provide a simple, repeatable solution for this. Keep an eye on what is getting published to npm, in the @wordpress organization scope.
We do need to tell Jest to use that setup file. Here is a complete Jest setup to add to package.json:
React and WordPress
I’ve written quite a bit about choosing between React and VueJS. Before Gutenberg, I was on team VueJS. But, learning Gutenberg development required me to take a deep dive into React and re-evaluate my original, negative opinions of JSX — the templating language that is generally used for React components.
You do not need to know React to develop for Gutenberg, but it really helps. You also do not have to use React. I have used Vue for block UI, using Gutenberg to supply state to Vue components. It’s pretty cool actually, but it’s an unnecessary layer of complication that would require a lot of good reasons to keep both Vue and React in your webpack bundles and have to think about both frameworks.
Vue and React manage state very differently, so keeping the rules of both, and the different syntax of the templating languages doesn’t scale mentally.
Redux(-like) State Management With WordPress Data
So, I like Vue a lot, but once WordPress made me reconsider JSX again and see how it could be used really well in a WordPress plugin, I was sold on React. One pain point for me with Vue was state management. I felt that Vuex, the recommended state management solution required too much boilerplate and was hard to integrate with components without effectively creating global state. I was probably doing it wrong, but Vuex just never clicked for me.
Redux, which is the standard — for now — for state management in React apps, makes a lot more sense to me than Vuex. That’s my #2 reason for moving to React. Because WordPress now uses an abstraction on top of React for Gutenberg, using both Vue and React does not seem practical. The more we integrate with Gutenberg, the more using React is the simplest solution. I also love how the pattern of using container and presentational components with React + Redux help keeps concerns separate and unit tests simple.
The Redux abstraction in Gutenberg, available on npm as @wordpress/data, makes Redux simpler. By registering a “store” with @wordpress/data actions and reducers are linked and there are utilities for subscribing to changes, selecting data, making API requests and higher-order components for injecting state.
In my last post for Torque, I covered the basics of state management for Redux with WordPress. The example code was taken from the Caldera Forms processors UI library which I am working on. This is an example of a use case that has to work in the post editor, in other wp-admin screens and outside of a WordPress environment, since Caldera Forms Pro is a Laravel and Node app.
For example, here is a presentational component that encapsulates the entire processor UI, without any state management.
We can call this component “controlled”. It’s not aware of state, its state is totally controlled by some other system. This component gets “wrapped” in the withSelect higher order component, so it can access data from the store.
This component is a container for the components that make up the UI. It’s responsibility is to compose the UI with child components that use the container’s props.
Note that I’m using the prop-types library to tell React what types of props the component must receive. I love doing this. Using prop-types provides strong typing for React components without having to learn and setup Flow or Typescript. I do use Flow on some projects, but for the most part I find prop-types to be more than enough validation.
What I really like about prop-types is if I fail to follow the rules, my Jest snapshot tests will not work, and the errors they raise when they fail will tell me which component is being passing the wrong props and where.
This presentational component gets “wrapped” in the withSelect higher order component (HOC), so it can get data from the store and provide that to the presentation component.
The withSelect HOC lets us read data from state. We also need to send changes to the store. We do this by wrapping the component with the withDispatch HOC:
Here is the complete wrapped component, that can select from state and dispatch changes in state:
There is no coupling between Redux and the store. The rules of the connection, which the wrapper component has the sole responsibility for, is defined based on the component props, which are just arguments passed to a class. The presentational component is passed the prop with the change handler function and it calls it. What that function does is the responsibility of a different component. As long as the public API of the component and the change handler function stay the same, any change can happen in the component or function with that responsibility. Unit tests with Jest detect these changes.
With Jest, we do not want to test React. We want to test that our component has the right props and if there are change handlers, they fire and emit the right data. Jest has a great snapshot testing tool. This tool renders the component and stores it as JSON. Then in future runs the snapshot is recreated and compared to the stored snapshot. Any changes and the test fails.
When these types of tests fail, that indicates that the different props were passed to the component or one of its children. This could be intentional, and that’s fine, you can just save a new snapshot. Or it means that they way that your components are wired together has changed in an unintended way and a regression bug has been introduced.
That’s It
There is nothing fancy here. Set up webpack imports and use them, installing dependencies as needed. If you’ve learned to use npm and use it for React development, you know how to do this. It’s exciting to see WordPress start to work more like a modern web app in this way. Much DUX.
Once you have everything setup to import with webpack and have the wp global managed, which environment you are in Gutenberg, WordPress other or not WordPress does not really matter. That’s really cool.
1 Comment