A Practical Overview of the @wordpress/data API
- WordPress Data Series Overview and Introduction
- What is WordPress Data?
- WordPress Data: How to Register a Custom Store
- WordPress Data Interlude: Let’s talk Apps
- WordPress Data Store Properties: Actions
- WordPress Data Store Properties: Selectors
- WordPress Data Store Properties: Controls
- WordPress Data Store Properties: Resolvers
- WordPress Data Store Properties: Action Creator Generators
- WordPress Data Store Properties: Reducer
- WordPress Data: Putting it all together and registering your store!
- WordPress Data: Interfacing With the Store
- WordPress Data: Registry
- WordPress Data: Debugging Tips and Tools (draft)
- WordPress Data: Testing Tips (draft)
The WordPress data module (or package) is a critical component of the new WordPress Editor as demonstrated by it’s implementation in (at the time of writing) 7 other WordPress packages:
@wordpress/block-directory
registers a store with thecore/block-directory
namespace.@wordpress/block-editor
registers a store with thecore/block-editor
namespace.@wordpress/blocks
registers a store with thecore/blocks
namespace.@wordpress/core-data
registers a store with thecore
namespace.@wordpress/edit-post
registers a store with thecore/edit-post
namespace.@wordpress/editor
registers a store with thecore/editor
namespace.@wordpress/notices
registers a store with thecore/notices
namespace.
What do I mean by “namespace” in the above list?
We’ll get into this in more detail later on, but as a quick answer, wp.data stores can be registered to their own state object identified by a given string which is referred to here as their namespace. So to interact with the data stored in a given state, you need to know and reference it’s namespace.
So what is wp.data
and why was it created? From the guide is the following description:
WordPress’ data module serves as a hub to manage application state for both plugins and WordPress itself, providing tools to manage data within and between distinct modules. It is designed as a modular pattern for organizing and sharing data: simple enough to satisfy the needs of a small plugin, while scalable to serve the requirements of a complex single-page application.
The data module is built upon and shares many of the same core principles of Redux, but shouldn’t be mistaken as merely Redux for WordPress, as it includes a few of its own distinguishing characteristics.
I’m going to pull a few key words and phrases from this description and zero in on them.
Application State and Tools
In any given application, there are various contexts where state comes in to play. Typically, when the term state is used, it refers to a snapshot of a set of data that is being used by something in the application at a given point of time.
So in our given product directory example we’re going to be using through this series, state could refer to the current list of products available for displaying to the potential customer at the time they load that initial view of products.
In React, there are various contexts in which you might find state:
Local State:
This is typically at the component level and it deals with state only that particular component cares about. Typically nothing outside that component cares about that state.
Tree State:
This is the state that relates to a slice of a given React component tree. Typically in this setup you’ll have a parent component that maintains the state for all its children and passes that state through via props or react context.
Sidenote: Tree state isn’t a term you’ll find referenced much elsewhere and is often considered the same as local state. I find it helpful however to differentiate between state that is specific to a component vs state that lives across a react tree (but isn’t global). But in most react/redux documentation you’ll only find two types of state referenced: Local and Global (or application).
Application State:
Application state is sometimes referred to as a “global” state and it lives outside of the rendered component tree. Components interact with this state via whatever interface is exposed to the components for doing so.
This might be in the form of a simple global object, or something like Redux and MobX.
WordPress data is a form of application state. It exposes various interfaces (or tools) for both creating and maintaining this application state. We’ll take a closer look at those various tools (which we’ll refer to as it’s api) in later posts.
Modular Pattern
There are two aspects to the modular pattern used in wp.data
: it is published as it’s own package, and the pattern for how stores are registered and consumed.
As a published package, wp.data can be consumed by any code needing it without bringing in the weight of any additional stores. Yet, via it’s apis any registered store states can be interacted with via a common interface.
Stores are registered and consumed individually yet can be interdependent within the same registry. So for instance, an application can consume selectors or actions from any registered store (or even cross stores within their own selectors or actions).
Further, later in this series, I’ll spend a bit of time introducing how you can create your own wp.data registry that inherits from a parent which allows for having descendant modular registries in use within a react component tree!
Share same core principles of Redux
WordPress data shares three basic principles with Redux with some slight variations in some cases:
Single source of truth
This principle in redux is:
The state of your whole application is stored in an object tree within a single store.
This principle diverges a bit in wp.data
in that you may have multiple namespaced stores kept within a given registry. So each store has its own state tree. Generally, it’s also a best practice to keep the state for a store serializable. Don’t put anything inside it that you can’t easily turn into JSON.
State is read-only
The only way to change the state is to emit an action, an object describing what happened.
What this means is you don’t ever mutate state but instead dispatch action objects that help describe the intent for transforming the state.
Changes are made with pure functions
To specify how the state tree is transformed by actions, you write pure reducers.
The registered reducer for a given store should be a pure function that receives the previous state and action and returns the next state. The reducer should not perform any side-effects and should never mutate the state. It either returns the existing state (if there are no changes) or a new state object (having changes incorporated in it).
Distinguishing Characteristics
While wp.data
has a lot of similarities with redux including the way parts of the API are named (selectors, action creators, reducer etc), there are still some key differences that are notable.
Modularization Pattern
In redux, there is generally only one global state. In wp.data, you can isolate slices of state to a namespaced store which has the following impacts:
- Dispatched actions only surface in the reducer for the store they apply to.
- Subscribers fire on changes to any registered store state within the given registry. This is because it is possible that implementing code may be selecting from multiple registered stores in a subscriber callback.
Separation of mapStateToProps
from mapDispatchToProps
In React Redux, these two mapping functions are combined in the connect
api, whereas in wp.data
there is a clear distinction via the usage of withSelect
and withDispatch
(or the siblings useSelect
and useDispatch
). In wp.data, dispatch is not dependent on a subscription to state changes and this allows for state-derived values to be used. With that said, it’s still possible to dispatch actions in withSelect
or useSelect
.
Subscribe is only called when the state has changed.
In Redux, a subscribe
listener callback is called on every dispatch, regardless of whether the value of the state has changed or not. In wp.data
, a subscriber is only called when the state has changed.
Select mapping function can return undefined
if it has no props to inject.
In React Redux, the mapStateToProps
function must return an object. In wp.data
, a withSelect
mapping function can return undefined
if it has no props to inject.
Also, in React Redux, the mapDispatchToProps
argument can be defined as an object or a function. In wp.data
, the withDispatch
higher-order component creator must be passed a function.
In the next post in this series we’re going to start diving into the wp.data api for registering a store!