WordPress Data Store Properties: Selectors

This entry is part 6 of 15 in the series, A Practical Overview of the @wordpress/data API

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.

Series NavigationWordPress Data Store Properties: ActionsWordPress Data Store Properties: Controls
  1. 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 named reviews and have same folder structure as products. However, I am wondering how would you integrate (registerStore) products and reviews 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.

    1. If you wanted to have products and reviews in the same state you could just have a state shape with something like:

      const state = { products: [ ], reviews: [] }
      

      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.

      It would be great to see example in your series of posts about @wordpress/data API (of course if you have time for that).

      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.

Leave a Reply

Up Next:

WordPress Data Series Overview and Introduction

WordPress Data Series Overview and Introduction