It’s only recently occurred to me that I’ve been doing WordPress, PHP, and JavaScript development for a while. In the last 5-6 years, I’ve learned some best practices that are no longer the best. For example, state management — the practice of putting all of the dynamic values of your application — a user inputs to a form, the current page’s posts, etc. — in one place, was not common. Now, I can’t imagine life without all of my state in one place.
When I started writing JavaScript, if I needed to know or update the value of a form field, I used jQuery’s val() method. If I needed to do something every time it changed, I’d subscribe to its change with jQuery’s change() method. Of course val() doesn’t trigger the change event, so I had to remember to use jQuery.trigger() for that.
That’s an example of a pattern that starts simply — nothing complex to learn — and gets unstable and anti-performant quickly. Learning to use a state management solution, such as Redux is an extra complication that takes time. State management, when used correctly, is the kind of extra complication in an application’s structure that makes developing and testing the application less complicated.
Technology moves fast and now the best practice is state management. State management, using a tool like Redux, which is very popular for React apps, creates single source of truth for application state. This is much like using a container, a topic I have covered in a PHP context.
In this article, I’m going to cover what problems state management tools like Redux solve. In my next post, I’ll be covering sharing components between React apps and Gutenberg blocks. A key part of that post covers using WordPress’ Redux abstraction. Let’s start with why.
Single Responsibility of State Management
The single responsibility principle, which I’ve written about before, enforces a simplicity on our code, as each function can only do one thing.
The Single Responsibility Principle (SRP) states that each software module should have one and only one reason to change.
State-Management’s single responsibility is to manage state. In a React app, we can have “dumb” components that have their state supplied to them as props. That state can come from parent components they are nested in, Redux, mock data in tests, whatever. They are pure functions — functions that take inputs and create output without side effects — and therefore decoupled from state management.
If the presentation of the data changes, that change happens in the React component. If the way we handle updating the data happens, that change happens in state management.
We call Redux, or Vuex, or Flux, or whatever you are using, the single source of truth for state because it’s the only place state really lives. Everything else is observing state.
A Few Basic Concepts
In this section, I’m going to walk through some basics of state management with Redux, which Gutenberg uses. Discussing these concepts, using Redux as a concrete example is useful for WordPress and React development and prevents me from just writing about abstract concepts. I’ll note how these concepts are implemented in Vuex as well so you can see these are patterns implemented beyond React + Redux.
These examples, and the examples in the slides from my talk at the JavaScript for WP conference are from the Caldera Forms processor UI module’s state management. They are simplified for clarity. I recommend taking a look at that code on Github for a complete, and more practical example. I did refactor the examples to use an object instead of a Map. That’s worth noting as I definitely recommend starting with an object, but consider other data types that might be easier to manage.
Reducers
In Redux, state is managed by reducer functions. In functional programming reducers, for example, Array.reduce() in JavaScript, take a collection of inputs and reduce them to a single value. That single output value could be any type. You could count inputs and return a total as an integer.
A Redux reducer takes the current state and returns the new state. Because React will only re-render if the object it is given has changed — not if it has been mutated — we almost always want to return a new object in our Reducer.
Generally, we use one reducer for a collection of data and specify specific actions for a reduce to act on. In Redux we call these actions, in Vuex, we call them mutations. Mutations is a better term in my mind as they mutate state, but actions is a good metaphor to as we send these actions into Redux, which maps them to the right reducer, and applies middlewares.
Redux reducer functions typically take an object as the second argument, this is the action, and use a switch control structure on action.type. For example:
Notice that I’m using the spread operator when returning state. My goal was to add to the array store in state.processors. I could have used state.processors.push() to mutate that array and then returned state. The object would have updated the way I wanted, but React would not update as a result.
Why? Because mutating the array (a type of object) didn’t create a new object, it updated the object that React had been supplied a reference of. We have to supply React with a new object.
This is a problem that Immutable.js solves. I recommend, when you’re getting started with Redux that every time you get annoyed that React isn’t updating properly, you assume it’s because you mutated an object instead of creating a new one. That’s most likely the problem. Also, read the introduction to Immutable.js, but do not actually use it. You might use Immutable.js later, but trying to learn that and Redux and React at once, too much and you probably do not need that extra level of complication.
Actions
If you look at this super simple reducer, we can assume that actions will have a certain “shape.” This is the structure of the object that must be created every time we dispatch this action. In order to keep that object’s shape consistent throughout the application, we add an action creator function.
These functions are also pure functions and have the responsibility of defining the shape of the action.
Action functions are strongly coupled to a reducer. I say this because if I need to change the shape, the reducer and the action need to change. I’m OK with that because it works, it’s a standard, and we can say the pattern of action and reducer (and selector) have the responsibility for managing a specific data point in state. And I like that because its a repeatable pattern that gets refactored together when a change needs to be made.
Selectors
Actions tell reducers to mutate state. Awesome, but we also need a way to get data out of state and into our tests or API requests. That’s where selectors come in. Selectors are responsible for getting the right data, in the right form from state.
In Vuex, the equivalent to Redux selectors are getters. I like that name, getters or Redux selectors are functions that get data.
For example, if your state object has an array of posts in it, you might add a selector function for finding post by ID:
Getting back to the examples from the Caldera Forms Processor UI library, here is the selector for getting a processor by ID from state:
Adding selectors to an app requires a library like reselect or WordPress’ data module, which I’ll cover in my next post.
Resolvers
Everything I’ve shown so far has used pure functions. These functions have to be synchronous. IN Redux, middlewares are used for asynchronous actions. This is probably the most complex part of setting up Redux, in my experience.
The simplest solution, I’ve found is WordPress’ data module. In a WordPress data store, a “resolver” can be added. This resolver function can be asynchronous and is called when a selector of the same name is called for the first time, with a given argument(s).
For example, if we had a selector and a resolver called getPost, for a store that tracked an array of posts, the first time, the getPost selector was called a WordPress REST API request would be made, with the result set in state. Subsequent sections of this post would just use the value in state. This is a nice, simple implementation of lazy-loading for data.
If you had a selector and resolver that queried by page, infinite scroll and load more type interfaces become simple to manage.
Here is a practical example, abstracted out from Caldera Forms’ Gutenberg block:
That’s The Basics
That’s more than enough info to get started with Redux. I recommend trying the counterexample and that you check out this article on Redux + React design patterns by Dan Abramov of the React Team. I also presented on this topic at the JavaScript For WP conference, you can see my slides here.
In my next post, I’ll show you how to use WordPress’ Gutenberg data module to wire Redux-managed state into a React app, including Gutenberg blocks.
1 Comment