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 job of selectors is to provide an interface to access state from the registered data store. With wp.data this is the primary way that your store state is exposed and allows you to encapsulate logic surrounding exposing that data. For instance, let’s say you just want to store all products in the store state as a simple array of product objects:
const state = [
{ id: 1, name: 'Scarf', price: '1025' },
{ id: 5, name: 'Hat', price: '356' },
];
You could create a selector that returns the entire state and then any components wanting just a specific product could filter that state:
export const ProductName = ( { productID } ) => {
const products = useSelect( ( select ) => select('mystore').getAllProducts(), [] );
//get just the product we want to use.
const product = products.filter( ( product ) => product.id === productID );
return <h1>{ product.name }</h1>
};
Sidenote: In the above example useSelect
is something you might not know about yet. I will cover this later in the series but for now, it’s sufficient to understand that getAllProducts
is the selector being called here.
In the example, we have a getAllProducts
selector that is returning all the products stored from the store state and then the component is taking care of filtering that to just the product needed. However, I can do better, if we move the filtering logic into a selector:
export const ProductName = ( { productId } ) => {
const product = useSelect( ( select ) => select( 'mystore' ).getProduct( productId ), [ productId ] );
return <h1>{ product.name }</h1>;
};
Now instead of having to do the same filtering logic in every component where I need a single product from the store state, I can just use the getProduct
selector.
Sidenote: I usually avoid getting too granular with the created selectors initially to avoid over-engineering and creating selectors that might never be needed. So even with the example above, I probably would have just started with a getProducts
selector first and then only add the more granular getProduct
selector if I saw a repeated pattern in my components of getting a single product out from the array of products.
So selectors are basically your window into retrieving what slice of state you need from the store. With that in mind, let’s dive into creating our selectors in our app. First, here’s the rough shape of what selectors look like:
export const someSelector = ( state, name ) => {
return state[ name ];
};
Let’s break this down:
- the selector is a function (preferably pure).
- every selector receives the current state as the first argument.
- you can define an arbitrary number of arguments the selector receives depending on your needs. In this example, our state is an object and I’ve indicated the first argument is a
name
used to return a specific property value from the state object. - the selector returns a value from the state using the arguments provided.
Sidenote: When the wp.data
api calls your defined selector, it will automatically inject and pass along the current state as the first argument along with any other arguments the selector is invoked with. So even though you define your selectors with state
as the first argument, you will call your selectors without the state argument (you’ll actually be calling a created function created by the wp.data
api that maps to your selector).
With this in mind then, going to our app, what is one selector you know we’ll need for sure to interface with the state?
Something to retrieve the products right? So let’s create a selector that gets the products from the store state. We’ll create a file for holding our selector called selectors.js
. You should now have the following folder structure in your app: src/data/products/selectors.js
. If it isn’t already, go ahead and open up the selectors.js
file. Go ahead and give a go at creating your selector and come back when you’re done (don’t cheat and read ahead! Try to see how well you understand the concepts so far first).
All done? If I’ve done a good job explaining selectors so far in this, and you have the same idea of what shape the state will be in our store as I do, then in this file, you’ll probably have at least something like this (might not be exactly the same, but generally the same thing):
export const getProducts = ( state ) => state.products || [];
Let’s break this down. I decided that I’m going to store products in the state in a property called products
(how original!). I also indicated what will be returned if the products
property isn’t defined yet (or is non-truthy) in the state. I like returning the same data type as what I expect to store things in so here, I’m returning an empty array. From this you can infer that we plan to store products in the state as an array.
Sidenote: Why am I storing products as a property on the state instead of just on the root object as an array of product objects? I tend to follow this pattern for the state shape in created stores to avoid painting myself in a corner in future iterations where I might want to keep additional data in the state. This often limits any refactoring I might have to do down the road.
Now that we have our selector in place for our app, we likely are going to initially retrieve the products from somewhere (via an REST request) right? Something unique to wp.data
that helps with this is a thing called resolvers
. But before we jump into the world of resolvers, we need to first learn a little bit about the controls
property. This will be the next post in the series.
Hi Darren,
Thank you for posting these series.
You have mentioned that your state has property
products
, so it’s easier to extend the state in the future.So, for example, if you decide to add
reviews
to your state then I assume that you would create folder namedreviews
and have same folder structure asproducts
. However, I am wondering how would you integrate (registerStore)products
andreviews
into one state, based on functionality from different folders and files (products and reviews).It would be great to see example in your series of posts about @wordpress/data API (of course if you have time for that).
Again, thank you very much for posting on how to use @wordpress/data API. For me, this is the best source of learning that API.
If you wanted to have products and reviews in the same state you could just have a state shape with something like:
Then that state would just be registered to a single store.
Folder and file structure is completely arbitrary and up to you how you organize it.
I’m not sure what you’re referring to here, can you expand on this? The entire series is about the
@wordpress/data
API? There are additions to the API since I first wrote this series which aren’t in it yet that I plan on updating at some point in the next few months.