WordPress Data Store Properties: Reducer

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

In the exploration through wp.data, we’ve already created the necessary properties needed to register the custom products store for our example app. This includes, action creators, selectors, controls, and resolvers.

Now it’s time to get to the last property we need for registering our store, and that’s the reducer. The reducer is arguably the most important part of the store registry and in fact it’s the only option you are required to include when registering a store.

The reducer function describes the shape of your state and how it changes in response to actions dispatched to your store. It will receive the previous state and an action object as arguments and should return an updated state value (or the same state object if nothing has changed). Here’s a very basic example reducer:

export const reducer = ( state, action ) => {
   if ( action.type === 'DO_SOMETHING' ) {
        return {
            ...state,
            ...action.data
        }
   }
};

There’s a couple principles you should follow with your reducer function:

  • It must be a pure function.  Pure functions are functions that have no side effects.
  • It should never mutate the incoming state, but return a new state with any updates applied. With that said, you can split up a large state tree so that each branch is managed by it’s own reducer and then combine them all into one reducer function (using combineReducer).

With that in mind, feel free to give a go at writing a reducer for our example app and when you’re done, come back here and compare with what I’ve put together.

Done? Alright let’s compare:

import TYPES from "./action-types";
 
const { CREATE, UPDATE, DELETE, HYDRATE } = TYPES;
 
const reducer = (
 state = { products: [] },
 { products: incomingProducts, product, productId, type }
) => {
 switch (type) {
   case CREATE:
     return {
       products: [...state.products, product]
     };
   case UPDATE:
     return {
       products: state.products
         .filter(existing => existing.id !== product.id)
         .concat([product])
     };
   case DELETE:
     return {
       products: state.products.filter(existing => existing.id !== productId)
     };
   case HYDRATE:
     return { products: incomingProducts.products };
   default:
     return state;
 }
};
 
export default reducer;

Let’s breakdown the above example a bit. What is happening?

First, our reducer function is defining a default value for the state argument of { products: [] }. This ensures that if the state has not been initialized yet, it will assume this value. It’s a great way to describe the shape of the state in your store (especially if your store is fairly simple with a single reducer function).

Second, our reducer function is checking what type is coming from the provided action object and defining what to do if a specific type is passed in on the action object. So for instance, this particular reducer is listening for the CREATE, UPDATE, DELETE, and HYDRATE type constants and reacting accordingly.

Third, each type has logic associated with it that will update the state using the data provided by that action object. So an action object with the CREATE action type constant value will return a new state object with the passed in product added to the products array. An action object with the UPDATE action type constant value will return a new state object replacing a product in the products array with the incoming product (or adding it if it doesn’t exist in the state yet). An action object with the DELETE action type constant value will return an new state object with the products array excluding the provided product.id. An action object with the HYDRATE action type constant value will replace the products array in the state (with the provided array of products).

Note: Notice how the HYDRATE action type is using the products key on incomingProducts. This is because the dummy data that the dummyjson.com service is returning has that actual products on that key.

Finally, if the action object does not have a matching type for what the reducer is watching for, then the current state is simply returned.

The reducer plays an important role in a wp.data store because it’s the method by which the state is updated and signals to the app subscribing to the store any state changes.

In the next post of this series we can finally put all the parts together to register our store!

Series NavigationWordPress Data Store Properties: Action Creator GeneratorsWordPress Data: Putting it all together and registering your store!

Leave a Reply

Up Next:

WordPress Data: Interfacing With the Store

WordPress Data: Interfacing With the Store