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)
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!