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
Before we took our side detour into learning about the controls
property, I mentioned that since we now have a selector for getting products, it’d be good to implement a way to retrieve the products from the server. This is where resolvers
come in.
Let’s get started by creating a resolver for retrieving products. If you’re following along, create a new file in the store folder called resolvers.js
. Your file structure should end up being src/data/products/resolvers.js
. Add this to the file:
import { fetch } from "../controls";
import { hydrate } from "./actions";
import { getResourcePath } from "./utils";
export function* getProducts() {
// the dummyjson.com service returns paginated results. For the purpose of this demo
// we only need 10 hence the limit param.
const products = yield fetch(getResourcePath() + "?limit=10");
if (products) {
return hydrate(products);
}
return;
}
In our example, the following things are happening:
- This first expression yields the
fetch
control action which returns the response from thewindow.fetch
call when it resolves. If you think of theyield
acting like anawait
here, it might make more sense. The return value is assigned toproducts
. - If there is a truthy value for products, then the
hydrate
action creator is called and the resulting value returned which essentially results in dispatching the action object for hydrating the state.
Note: getResourcePath()
is just a utility I created for setting up the url interacting with the dummyjson API. It should be in the products/utils.js
folder already for you. Note, in the above example I’ve also added a limit parameter since we don’t need the full list of products the service returns.
The hydrate
creator is new here, we haven’t created it yet. This is a good opportunity to think through what you may need to do for the hydrate action. So take a few minutes to try that out yourself and then come back.
Back? How did it go? You should end up with something like this in your action-types.js
file:
const TYPES = {
UPDATE: "UPDATE",
CREATE: "CREATE",
DELETE: "DELETE",
HYDRATE: "HYDRATE"
};
export default TYPES;
Notice that we’ve added the HYDRATE
action type here. Then in your actions.js
file you should have something like this:
export const hydrate = products => {
return {
type: HYDRATE,
products
};
};
Now that you have that good to go, I want to take a bit of time here to let you know about some important things to remember with resolvers:
The name of the resolver function must be the same of the selector that it is resolving for.
Notice here that the name of this resolver function is getProducts
which matches the name of our selector.
Why is this important?
When you register your store, wp.data
internally is mapping selectors to resolvers and matches on the names. This is so when client code invokes the selector, wp.data
knows which resolver to invoke to resolve the data being requested.
The resolver will receive whatever arguments are passed into the selector function call.
This isn’t as obvious here with our example, but let’s say our selector was getProduct( id )
our resolver will receive the value of id
as an argument when it’s invoked. The argument order will always be the same as what is passed in via the selector.
Resolvers must return, dispatch or yield action objects.
Resolvers do not have to be generators but they do have to return (or yield, if generators) or dispatch action objects. If you have need for using controls (to handle async side-effects via control actions), then you’ll want to make your resolver a generator. Otherwise you can just dispatch or return action objects from the resolver.
At this point you may have a few questions:
- How does the client know what selectors have resolvers?
- Can I detect whether the selector is resolving? How?
- Is the resolver always invoked every time a selector is called?
Before answering these questions, I think it’ll help if I unveil a bit of what happens in the execution flow of the control system in wp.data
and the implementation of generators in resolvers.
So let’s breakdown roughly what happens when the getProducts
selector is called by client code using the following flow chart:

Let’s step through this flowchart. When you call a selector, the first thing that happens is the selector returns it’s value. Then asynchronously, some wp.data logic also checks to see if there is a related resolver for the selector. If there is a resolver, then some internal logic will be used to determine whether or not resolution has started yet, or has finished.
Let’s pause here for a couple minutes and jump into a little side trail about resolution state.
Having a resolver that handles side-effects (usually asynchronously retrieving data via some sort of api) introduces a couple problems:
- How do we keep the resolver logic from being executed multiple times if it’s initial logic hasn’t been completed yet (very problematic if we’re making network requests)?
- How do we signal to client code that the resolver logic has finished?
In order to solve these problems, wp.data
automatically enhances every registered store that registers controls and resolvers with a reducer, and a set of selectors and actions for resolution state. This enhancement allows wp.data to be aware of the resolution state of any registered selectors with attached resolvers.
The resolution state keeps track by storing a map of selector name and selector args to a boolean. What this means is that each resolution state is tied not only to what selector was invoked, but also what args it was invoked with. The arguments in the map are matched via argument order, matching primitive values, and equivalent (deeply equal) object and array keys (if you’re interested in the specifics, the map is implemented using EquivalentKeyMap).
With this enhanced state (which is stored in your store state on a metadata
index), you have the following selectors (for usage examples, we’ll use our getProducts
selector as an example) :
Selector | Description | Usage |
getIsResolving | Returns the raw isResolving value for the given selector and arguments. If undefined , that means the selector has never been resolved for the given set of arguments. If true, it means the resolution has started. If false it means the resolution has finished. | wp.data.select( ‘data/products’ ).getIsResolving( ‘getProducts’ ); |
hasStartedResolution | Returns true if resolution has already been triggered for a given selector and arguments. Note, this will return true regardless of whether the resolver is finished or not. It only cares that there is a resolution state (i.e. not undefined) for the given selector and arguments. | wp.data.select( ‘data/products’ ).hasStartedResolution( ‘getProducts’ ); |
hasFinishedResolution | Returns true if resolution has completed for a given selector and arguments | wp.data.select( ‘data/products’ ).hasFinishedResolution( ‘getProducts’ ); |
isResolving | Returns true if resolution has been triggered but has not yet completed for a given selector and arguments. | wp.data.select( ‘data/products’ ).isResolving( ‘getProducts’ ); |
getCachedResolvers | Returns the collection of cached resolvers. | wp.data.select( ‘data/products’ ).getCachedResolvers(); |
You will mostly interact with resolution state selectors to help with determining whether an api request within a resolver is still resolving (useful for setting “loading” indicators). The enhanced resolution logic on your store will also include action creators, but typically you won’t need to interact with these much as they are mostly used internally by wp.data
to track resolution state. However, the resolution invalidation actions here can be very useful if you want to invalidate the resolution state so that the resolver for the selector is invoked again. This can be useful when you want to keep the state fresh with data that might have changed on the server.
As with the selectors, the action creators receive two arguments, the first is the name of the selector you are setting the resolution state for, and the second is the arguments used for the selector call.
Action Creator | Description | Example Usage |
startResolution | Returns an action object used in signalling that selector resolution has started. | wp.data.dispatch( ‘data/products’ ).startResolution( ‘getProducts’ ); |
finishResolution | Returns an action object used in signalling that selector resolution has completed. | wp.data.dispatch( ‘data/products’ ).finishResolution( ‘getProducts’ ); |
invalidateResolution | Returns an action object used in signalling that the resolution cache should be invalidated for the given selector and args | wp.data.dispatch( ‘data/products’ ).invalidateResolution( ‘getProducts’ ); |
invalidateResolutionForStore | Returns an action object used in signalling that the resolution cache should be invalidated for the entire store. | wp.data.dispatch( ‘data/products’ ).invalidateResolutionForStore(); |
invalidateResolutionForStoreSelector | Returns an action object used in signalling that the resolution cache should be invalidated for the selector (including all caches that might be for different argument combinations). | wp.data.dispatch( ‘data/products’ ).invalidateResolutionForStoreSelector( ‘getProducts’ ); |
Now that we know about the resolution state, let’s return to the flowchart. We left off at the point where wp.data has determined there’s a related resolver for the given selector and is determining whether resolution has started yet or not. Based on what we just looked at, you should know how it does this. Right! It’s going to call a resolution state selector. The specific selector called here is hasStartedResolution
. If that returns true, then wp.data will basically abort (because the resolver is already running, or completed asynchronously).
If hasStartedResolution( 'getProducts' )
returns false however, then wp.data will immediately dispatch the startResolution
action for the selector. Then the getProducts
resolver is stepped through. Now remember getProducts
is a generator. So internally, wp.data will step through each yield
it finds in the resolver. Resolvers and controls are expected to only yield action objects or return undefined.
The first value yielded from the resolver is the fetch
control action. Since wp.data recognizes that this action type is for a control it then proceeds to invoke the control. Recognizing that the control returns a promise, it awaits the resolution of the promise and returns the resolved value from the promise via calling the generators generator.next( promiseResult )
function with the result. This assigns the value to the products variable and the generator function continues execution. If there are no products, then the resolver returns undefined and this signals to the resolver routine that the resolver is done so wp.data will dispatch the finishResolution
action for the selector.
If there are products, then the resolver returns the hydrate action. This triggers the dispatching of the hydrate action by wp.data and the dispatching of the finishResolution
action because returning from a generator signals it is done. When the hydrate
action is processed by the reducer (which we haven’t got to yet), this will change the state, triggering subscribers to the store, which in turn will trigger any selects in the subscriber callback. If the call to the getProducts
was in subscribed listener callback, it will then get invoked and the latest value for getProducts
(which was just added to the state) will get returned.
That in a nutshell (a pretty big nutshell at that), is how resolvers work!
Sidenote: if you want to dig into the technical logic powering this, check out the @wordpress/redux-routine
package. This is implemented automatically as middleware in wp.data to power controls and resolvers, but can also be used directly as middleware for any redux based app.
Great series! =)
Do you have some suggestion to check the loading state for the actions? Maybe something similar to the
getIsResolving
from the resolvers.Currently
wp.data
doesn’t have anything automatically built in to track loading state for actions, but actions do return a promise, so you can use that to derive loading state. You just have to be careful that if you are waiting for the promise resolution within auseEffect
that you aren’t setting state after the promise resolves and when the component is rendering.Alternatively you can create your own state for tracking loading state. In WooCommerce we do that.
Hi Darren, great series (even if it’s still a lot over my head for now). How does the frontend
wp.data
resolve or fetch existing data from the WP database?And how can you add something custom to the API response? For example, in one of my plugins I’ve replaced the taxonomy panel with a custom panel. So I get the default taxonomy response with what looks like
/wp/v2/post_tag
https://github.com/WordPress/gutenberg/blob/master/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js#L229 (which maybe answers my first question?)If you are fetching a taxonomy, how would you go about including something custom in the response? For example, like taxonomy meta or in my case a boolean that’s editable via filter?
Hi Kathy,
Sorry for the delay in getting back to you.
It all depends on what stores are registered in the frontend and whether you have access to them (either via enqueuing or making sure your script loads after the store you want to use is registered).
There’s a couple components to this:
A handy tip you can use when finding out what’s available is that you can use the
wp.data
global directly in your browser console on any page that registers stores. So for example, if I open up my browser console on a post editor screen, I can do this and get a list of all the available selectors in the registeredcore
store:I can also call a selector and find out what the response is. Caveat, if you call a selector attached to a resolver, the first call initiates the request. So you will need to call it again after the request is complete to get the response from the request.
This is a great way to experiment and find out what’s exposed via the registered store.
Hi, Thank you so much for this series of posts!
I faced a situation and found a solution that works, but I’m wondering if I’m violating something by doing so.
I wanted my resolver to check if the data is already in the state before yielding the action that makes the API request. So if I already have the data in the initial state, no api request is needed.
Here’s what I did
*getConnectionStatus() {
const existingStatus = select( STORE_ID ).getConnectionStatus();
if ( existingStatus.hasOwnProperty( 'isRegistered' ) ) {
return existingStatus;
}
// make api request...
Note that I invoke the same resolver. I understand it will return the value it finds in the state and won’t trigger the resolver again because it knows it’s already running.
Does it look ok? Is it fine to use
select
from inside a resolver?Howdy! I’m not sure I understand why you need to do this though. Resolvers are intended to provide a mechanism for initially updating the state relevant to the selector if it has never been resolved yet. Once resolved, the resolver is never called again unless the resolution state is changed.
Are you prehydrating the state somewhere else?
Yes, via the initial state.
The goal is to give this option to the consumer my package. If the information is not provided in the initialState for any reason, the store will look for it. But if the info is already in the state, no need for a api request.
Okay, then in that case, whatever is doing the hydration should dispatch a
finishResolution
action with the appropriate arguments to let the store know that the state is resolved. Then when the selector is called it won’t hit the resolver.Thank you Darren for you replies.
The thing is the value is informed via the initalState option when the store is created. So there’s no event to trigger the
finishResolution
method.initialState
is passed as an option tocreateReduxStore
. So, in this case, the resolver will serve as a fallback in case the initial state is missing this information.Sorry if I didn’t make myself clear before.
In any case, we will trigger the
finishResolution
right after the store is created. I think it’s a more elegant solution. I was just wondering what the best practices wereGotcha, I’m basing my reply on the assumption that the hydration is still coming from the server.
I still think that it’d be best to keep complicated logic out of the resolver for knowing what the hydration state is (it shouldn’t really care) and work with how wp.data is architected. With that in mind, instead of hydrating via the
initialState
, what about creating an compound action explicitly for initial hydration of the state? So the logic in the action is roughly:“`
export function* hydrateStore(
initialState
) {
// whatever the action is that your resolver uses to save state to the store.
yield receiveState( initialState );
// yield ‘finishResolution’ action (sorry can’t remember the syntax offhand).
}
Just pushing an update here, looking at the source code, found that your resolver could be a function or object, if you pass an object, you could add isFulfilled key as function, that will receive the state and could check against it to allow or not the resolver call:
Reference:
https://github.com/WordPress/gutenberg/blob/trunk/packages/data/src/redux-store/index.js#L367-L369
https://github.com/WordPress/gutenberg/blob/trunk/packages/data/src/redux-store/index.js#L395