WordPress Data Store Properties: Actions

In the previous post of this series, we took a brief interlude to take a look at an app that is using tree state to manage the data the app is using. We discovered that, while the app was functional, some potential problems with using state this way were beginning to surface. In this post, we’re going to start learning about the various data store properties (the properties of the options object we use when registering our store) and in the process we’re going to convert this app over to use wp.data!  For starters, we’re going to focus on the products data in our store, so let’s just leave the cart data alone for now.

If you’re more of a hands on person, you can participate in this conversion by going to this sandbox I’ve prepared. It’s the same app as what I introduced in the previous post except with a few differences:

  1. We’re going to wire things up to an external server and to do this we’ll use a free service called jsonbox.io.  I’ve set up a simple component to help wire things up to this service as described on the first page of the loaded app. This will become important later when we start working on communicating with this service for our app. jsonbox.io basically allows you to send and retrieve arbitrary data from it’s api.
  2. You’ll see in the src folder a data folder.  We’ll use this folder to keep our new data store in.
  3. Inside the data folder you’ll see a controls.js file. While we will be referencing and using the @wordpress/data-controls package later on, the apiFetch control in that package is too tightly coupled to the assumption of working with a WordPress REST endpoint. So I’ve gone ahead and created a control that works with the native window.fetch api. Don’t worry about this for now, we’ll see how it’s used later on.

You can go ahead and fork this sandbox so that you can make it your own as we work through the first property of the configuration object for registering our store.

actions

I like to start with actions and selectors whenever I create a new data store as it helps isolate what kind of state I might need to retrieve and write to within the store. Even quickly mapping out a rough idea of this helps inform the shape the state might take and the interface needed to interact with it in my app.

Thankfully, because we’ve started by implementing the app using tree state, we have a rough idea of our needs just from examining the logic in the components.

Sidenote: Remember in the previous post I mentioned that I’ll avoid jumping to using global state in an application right away? The above is one of the reasons why. Once you do start building the wp.data stores, you’ll have a better idea of the requirements for what state you need to keep in wp.data and what interfaces you need for working with that state.

The actions object passed to your store should describe all the action creators available for your store. An action creator is a function that can accept optional arguments, and returns an action object to dispatch to the registered reducer. 

We’ll get to reducers later, but for now, think of the reducer as the thing that will handle updating the state tracked in wp.data. The dispatched action then is an instruction to the reducer on how you want the state updated.

Here’s a very basic action creator example:

export const recieveItems = ( items ) => {
    return {
        type: 'RECEIVE_ITEMS',
        items
    }
};

An action object is basically instructions for how the reducer should make changes to the state in the store. Dispatching actions is the mechanism by which changes are made to your state. Every action object has at a minimum, a type property with a string value identifying the action type. “Type” is an arbitrary string (usually capitalized) to describe the action. 

As you look over the current app, what are some actions you think we might need to take in modifying the state? Have any?

There’s at least three actions we’ll want to work with here: update, create, and delete.  One thing I like to do when I’m creating stores is to define my action types as constants. So I’ll often create an action-types.js file. First we’ll create a folder in the data folder called products and then a file inside that folder called action-types.js (this step is not a requirement, it is completely fine to just use your action types directly as strings).

Sidenote: Splitting the registerStore option property definitions into their own individual files is a pattern I picked up from the Gutenberg project and I like it as a way of organizing the various parts of a data store. Basically, the convention is to have each property isolated to it’s own folder: selectors.js for selectors, actions.js for actions, resolvers.js for resolvers etc. You don’t have to do things this way (especially for data stores that might not have a lot of interfaces) but it does help with finding things and maintaining.

So now you should have the following file structure src/data/products/action-types.js. Go ahead and open that file if you haven’t already. In there, let’s define some constants for the action types and export them on an object:

const TYPES = {
 UPDATE: "UPDATE",
 CREATE: "CREATE",
 DELETE: "DELETE",
};
 
export default TYPES;

Now that we have our types defined, we need to write our action creators. Let’s create a file called actions.js and add it to the same folder. In this file, go ahead and add some action creators using the action types we’ve defined. Think about what data you’d need for each action needing dispatched and include that in the arguments. Once you’ve given this a try yourself, come on back here for an example of the approach I’d probably take.

To go with the action types, I’ve created three action creators. I decided to call them createProduct, updateProduct, and deleteProduct and I ended up with this:

import TYPES from "./action-types";
const { UPDATE, CREATE, DELETE } = TYPES;
 
export function createProduct(product) {
   return {
     type: CREATE,
     product: result
   };
}
 
export function updateProduct(product) {
   return {
     type: UPDATE,
     product
   };
}
 
export function deleteProduct(productId) {
   return {
     type: DELETE,
     productId
   };
}

For the create and update action creators, I figured we’d need the product object, so the action creator will receive the product object as an argument. However for deleting the product, we probably only need to know the product id so that’s all that is received as an argument on the deleteProduct action creator.

There you have it! You’ve got some action creators that will be used to dispatch create, update and delete actions to the store reducer. In the next post we’ll move on to creating selectors for the store.

Series NavigationWordPress Data Interlude: Let’s talk AppsWordPress Data Store Properties: Selectors

Leave a Reply

Up Next:

WordPress Data: Interfacing With the Store

WordPress Data: Interfacing With the Store