WordPress Data: Registry

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

At various places so far in this series, you would have seen me use phrases like:

  • “registered store within the current registry”
  • “current registry dispatch” or “current registry select”

I’ve also mentioned that I’d get to this mysterious “registry” reference later in the series. Well, here we are!

What problem does the Registry solve?

As a part of diving into what the registry is in the context of wp.data, it’s good to take a brief look at what problem it solves and to do that we’re going to take a bit of a look at one of the cool features of the new Block Editor: reusable blocks.

With reusable blocks, you are able to select a group of blocks in the editor and convert them to a reusable block to be saved for reuse throughout your site in different posts/pages etc. It’s basically a saved instance of a specific set of blocks and content. This feature is neat because:

  • Common patterns for content can be saved for re-use on a site saving time in recreating that content everywhere.
  • You can edit a saved reusable block and any other place it is in use will automatically update with the new content.

Reusable blocks are actually powered by saving the content to a special post type created for them:

  • The contents of a reusable block are saved to a post type.
  • The reusable-block reference is saved to the content of the post it is used in.

Let’s look under the hood here. Let’s say I’ve created something like this:

I’ve saved this as a reusable block named “Test Reusable Block” and note it’s composed of three blocks: a paragraph block with the text “This is a reusable block test”; a quote block with the text “With a quote”, and a button block with the text “And a Button”. When I save the block and the post, what has happened under the hood?

First, the reusable block itself is saved as a post object for a custom post type called wp_block. The content of the block itself is saved to the post with this serialized block structure:

<!-- wp:paragraph -->
<p>This is a reusable block test</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p>With a quote</p></blockquote>
<!-- /wp:quote -->

<!-- wp:button -->
<div class="wp-block-button"><a class="wp-block-button__link">And a Button</a></div>
<!-- /wp:button -->

Notice the familiar block comment delimiters used for representing blocks in post content.

Now, let’s take a look at how the reusable block itself is saved to the post content for the post implementing it:

<!-- wp:block {"ref":825} /-->

Here we have wp:block which indicates this is a reusable block, and the serialized attributes of {"ref": 825}, 825 is the ID of the post the content for the reusable block is saved to.

So essentially reusable blocks are embedded content from one post, into the current post. This is important for the context of what I’m writing about here because one problem this introduced early on in the usage of reusable blocks was how do you edit this embedded content?

Originally what would happen is that the reusable block component would fetch the content for the particular reusable block from its post and then inject those blocks into the editors block list state (which itself was a data store in wp.data). However, this introduced some challenges with:

  • maintaining a cycle dependency between blocks in the post, and shared blocks from reusable blocks (affects everything from undo/redo tracking to extra complexity around keeping embedded blocks from reusable blocks from being edited separately outside of a reusable block edit context etc).
  • when fetching shared blocks from the server, it triggers the parsing of those blocks which means if they had already been rendered, the block will unmount/remount – which introduces a lot of overhead and performance impact.

So about a year ago, one of the project core contributors, Riad, introduced the idea of reimplementing reusable blocks as an embedded editor instance. Essentially, the editing and saving and rendering of a reusable block would end up self contained in it’s own editor instance and the main editor would only be concerned with the reference to the reusable block.

The path from the idea to the implementation took some work, but it paid off and ended up being a really great working solution.

Now back to the purpose of this particular post, how does reusable blocks relate to the wp.data registry? Well, one of the main things introduced as a result of this work was the concept of parent & child registries which allows for nesting editors using wp.data. In the context of reusable blocks, the editor instance for each reusable block could utilize the main editor’s global stores for things like viewport and various settings, but override the other data stores in the embedded editor for block state etc specific to that instance.

So essentially you may have multiple registry instances occurring in any given main editor instance content.

So… what is a registry (or registry instance) then?

In the context of wp.data, a registry is an object where all configured namespaced stores and their states are kept. wp.data itself creates a registry object and the initial api is exposed for that registry by default (so it is known as the default registry). Essentially this means whenever you do this:

import { registerStore } from '@wordpress/data'

registerStore( /** store configuration object **/ );

…you are registering your store with the default registry. It also means that any of the other exposed api from @wordpress/data is connected to the default registry:

// all of these functions come from the default registry
import { select, dispatch, subscribe } from '@wordpress/data';

In most cases and most apps, this isn’t an issue as you’ll only ever work with the default registry. However, if you are doing cross store selects or dispatches, this can become problematic if your custom store is used in the WordPress editor environment where reusable blocks might be in play because reusable blocks are not using the default registry. Instead, they are using a child registry created and used within the specific editor instance serving that block and it’s contents.

Hence a whole class of new functions were created to solve the problem of keeping entry points to a namespaced store state working within the correct registry. Before we get to that though, let’s start with the function used to create a registry!

createRegistry( storeConfigs = {}, parent = null )

This function creates a new store registry instance and it can receive an optional object of initial store configurations. Notice, that it also can receive a parent registry as an optional second argument! When the parent registry is provided, this serves as the fallback for any api calls to a namespaced store that is not found in the child registry. In those cases if a call to select( 'my/store' ) doesn’t exist in the child registry, then the parent registry will be checked to see if it exists there.

You can see this function used in two places (in application code) in the current block editor: The first is when creating the default registry. The second is in the withRegistryProvider HOC, essentially this HOC will return (by default) return a child registry implemented by nested components within that provider that was created with the current registry in the app context as the parent. The function is also extensively for wp.data related tests as it provides a quick way to get a mocked registry instance for testing with.

withRegistryProvider is where we get introduced to the RegistryProvider. The Registry provider is using React.Context to expose a specific registry to the children within that provider. The current registry in a tree can be read using the useRegistry hook (or RegistryConsumer if using older context syntax) which is useful if you want to grab the current registry within a component. In most cases you will never need to worry about these apis in code you build, but it is useful to know that the Block editor uses many of these for managing complex state in the app (withSelect, withDispatch, useSelect, and useDispatch all implement useRegistry under the hood to make sure they are working with the correct registry in the scope of their implementation).

Next, let’s move on to a the class of functions that ensure you’re working with the registry in the current context/scope of the function being called:

createRegistryControl

This is used for controls that will do cross store selects or dispatches and will be provided the registry in the current scope of when this control is invoked. You can see this function in use in the @wordpress/data-controls package where the dispatch and select controls are implementing this to ensure the selects and dispatches through the controls are happening in the right registry.

This function receives a curried function. That is, you provide it a function that receives a registry as an argument and then return another function that receives the control action object and returns the result of that control. For instance, I might create a control that retrieves an item from a specific store (this is contrived, in most cases you’d just use the select control from @wordpress/data-controls for this use case):

import { createRegistryControl } from '@wordpress/data';

// my control action
export const getItemById = ( id ) => return ( {
    type: 'GET_DATA',
    id
} );

// the control
export const controls = {
    GET_DATA: createRegistryControl(
        ( registry ) => ( { id } ) => {
            return registry.select( 'my store with items' ).getItem( id );
        }
    ),
};

createRegistrySelector

Similar to createRegistryControl, this function is used for selectors that will do selects from other stores within the same registry as the context of the current selector being invoked. It’s basically a way to expose selectors from other stores in a given store so you can keep your client implementation simpler. You can see examples of it in use here.

While it to receives a curried function as the argument, it differs from createRegistryControl in that the first function will receive the select interface from a registry (not the entire registry). The function you return would be what your selector arguments would have normally been.

The example I gave above doesn’t really need to be a control (assuming the selector doesn’t have an attached resolver), I could have done it directly in my store’s selectors if I wanted, but let’s enhance this example with getting a specific property value from the returned item (assuming the item is an object):

// in my store selectors.js file

import { createRegistrySelector } from '@wordpress/data';

export const getPropertyFromItemInOtherStore = createRegistrySelector(
    ( select ) => ( state, property ) => {
        const item = select( 'my store with items' ).getItem( id );
        return item[ property ];
    }
);

Finishing up

The wp.data registry can be a hard concept to wrap one’s head around but I hope I’ve helped pull back the curtain on the mystery surrounding it a bit in this post. As always, the comments are open if you have any questions or feedback (anything really useful I will use to update the post!).

You now have enough information to complete converting the cart data in our example application over to use wp.data (including cross store selections)! Give a go at doing that yourself as an exercise, or if you want to skip through to a completed example to play with, you can go here.

Thanks for sticking through with me in this series on wp.data. There’s still two more bonus posts to come, but everything you’ve read so far should jump start your usage of wp.data in your projects and get you on the road to being an expert user!

WordPress Data Store Properties: Action Creator Generators

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

Now we have a resolver for asynchronously getting products via a REST api endpoint, we have some action creators, and we have a selector.  We haven’t got to the reducer yet, but before we get to it, let’s consider another problem we haven’t addressed yet. 

When we update or create a product using our ui, how are we going to persist that to the server? We could just wire up the POST/PUT endpoints directly in our component, but remember one of the reasons we’re implementing wp.data is to provide a common interface for any component needing to interact with the products data. So what can we do here?

Fortunately, we’ve covered a part of the wp.data api that provides us with a mechanism for doing this, controls. With controls, it is possible to make action creator generators that yield control action objects for handling asynchronous behaviour.  Essentially everything you’ve learned about resolver generators so far can also be applied to action creators!

So let’s go back to the action creators we’ve already prepared (reminder: src/data/products/actions.js) which are createProduct, updateProduct and deleteProduct. Right now these are not wired up to any API route. Let’s change that. If you feel adventurous, why don’t you give it a go in your own fork of the app right now and see what you come up with. You can come back and see how close you got after.

You back? Okay, here’s one approach you could end up with:

import { getResourcePath } from './utils';
import { fetch } from '../controls';
import { select } from '@wordpress/data-controls';
import TYPES from './action-types';
import STORE_KEY from './constants';

const { UPDATE, CREATE, DELETE, HYDRATE } = TYPES;

export function* createProduct(product) {
 const result = yield fetch(getResourcePath(), {
   method: "POST",
   body: product
 });
 if (result) {
   return {
     type: CREATE,
     product: result
   };
 }
 return;
}
 
export function* updateProduct(product) {
 const canonicalProduct = yield select(STORE_KEY, "getProduct", product.id);
 const result = yield fetch(getResourcePath(canonicalProduct._id), {
   method: "PUT",
   body: product
 });
 if (result) {
   return {
     type: UPDATE,
     product
   };
 }
}
 
export function* deleteProduct(productId) {
 const product = yield select(STORE_KEY, "getProduct", productId);
 const result = yield fetch(getResourcePath(product._id), {
   method: "DELETE"
 });
 if (result) {
   return {
     type: DELETE,
     productId
   };
 }
}

Take note of a few things here:

  • For updateProduct and deleteProduct I need to get the original product in the state because jsonbox.io references objects on the server via an _id property. I could have used the existing getProducts selector and then filtered the product from the state, but I decided to create a new selector to handle that (which also keeps things DRY).  Here’s what I ended up with as a new selector in selectors.js:
export const getProduct = (state, id) => {
 return getProducts(state).find(product => product.id === id);
};
  • To access this new selector, I used the select control from the @wordpress/data-controls package. Why? This ensures we’re accessing the selector from the same registry (the importance of this will be explained in more detail in a later post in the series).
  • I’m using the fetch control (remember this from the post about resolvers?)
  • I’m returning the original action object that was here before the updates.

Notice that the action creator is still returning an action object, the only change here is that with controls, we’re able to do some side-effects before returning that action object.

In the next post of this series, it’s time to go on to the last property of our store registration configuration object, the reducer.

What is WordPress Data?

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

The WordPress data module (or package) is a critical component of the new WordPress Editor as demonstrated by it’s implementation in (at the time of writing) 7 other WordPress packages:

  • @wordpress/block-directory registers a store with the core/block-directory namespace.
  • @wordpress/block-editor registers a store with the core/block-editor namespace.
  • @wordpress/blocks registers a store with the core/blocks namespace.
  • @wordpress/core-data registers a store with the core namespace.
  • @wordpress/edit-post registers a store with the core/edit-post namespace.
  • @wordpress/editor registers a store with the core/editor namespace.
  • @wordpress/notices registers a store with the core/notices namespace.

What do I mean by “namespace” in the above list?
We’ll get into this in more detail later on, but as a quick answer, wp.data stores can be registered to their own state object identified by a given string which is referred to here as their namespace. So to interact with the data stored in a given state, you need to know and reference it’s namespace.

Read more “What is WordPress Data?”

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 };
   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).

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!

WordPress Data Store Properties: Resolvers

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

Before we took our side detour into learning about the controls property, I mentioned that since we now have a selector for getting products, it’d be good to implement a way to retrieve the products from the server. This is where resolvers come in.

Let’s get started by creating a resolver for retrieving products. If you’re following along, create a new file in the store folder called resolvers.js. Your file structure should end up being src/data/products/resolvers.js.  Add this to the file:

import { fetch } from "../controls";
import { hydrate } from "./actions";
import { getResourcePath } from "./utils";
 
export function* getProducts() {
 const products = yield fetch(getResourcePath());
 if (products) {
   return hydrate(products);
 }
 return;
}

In our example, the following things are happening:

  • This first expression yields the fetch control action which returns the response from the window.fetch call when it resolves.  If you think of the yield acting like an await here, it might make more sense. The return value is assigned to products.
  • If there is a truthy value for products, then the hydrate action creator is called and the resulting value returned which essentially results in dispatching the action object for hydrating the state.

Note: getResourcePath() is just a utility I created for setting up the url interacting with the jsonbox.io api. It should be in the products/utils.js folder already for you.

The hydrate creator is new here, we haven’t created it yet. This is a good opportunity to think through what you may need to do for the hydrate action. So take a few minutes to try that out yourself and then come back.

Back? How did it go? You should end up with something like this in your action-types.js file:

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

Notice that we’ve added the HYDRATE action type here.  Then in your actions.js file you should have something like this:

export const hydrate = products => {
 return {
   type: HYDRATE,
   products
 };
};

Now that you have that good to go, I want to take a bit of time here to let you know about some important things to remember with resolvers:

The name of the resolver function must be the same of the selector that it is resolving for.

Notice here that the name of this resolver function is getProducts which matches the name of our selector.

Why is this important?

When you register your store, wp.data internally is mapping selectors to resolvers and matches on the names. This is so when client code invokes the selector, wp.data knows which resolver to invoke to resolve the data being requested.

The resolver will receive whatever arguments are passed into the selector function call.

This isn’t as obvious here with our example, but let’s say our selector was getProduct( id ) our resolver will receive the value of id as an argument when it’s invoked. The argument order will always be the same as what is passed in via the selector.

Resolvers must return, dispatch or yield action objects.

Resolvers do not have to be generators but they do have to return (or yield, if generators) or dispatch action objects. If you have need for using controls (to handle async side-effects via control actions), then you’ll want to make your resolver a generator. Otherwise you can just dispatch or return action objects from the resolver.

At this point you may have a few questions:

  • How does the client know what selectors have resolvers?
  • Can I detect whether the selector is resolving? How? 
  • Is the resolver always invoked every time a selector is called?

Before answering these questions, I think it’ll help if I unveil a bit of what happens in the execution flow of the control system in wp.data and the implementation of generators in resolvers.

So let’s breakdown roughly what happens when the getProducts selector is called by client code using the following flow chart:

A flowchart describing the execution flow with resolvers in wp.data.

Let’s step through this flowchart. When you call a selector, the first thing that happens is the selector returns it’s value. Then asynchronously, some wp.data logic also checks to see if there is a related resolver for the selector. If there is a resolver, then some internal logic will be used to determine whether or not resolution has started yet, or has finished.

Let’s pause here for a couple minutes and jump into a little side trail about resolution state.

Having a resolver that handles side-effects (usually asynchronously retrieving data via some sort of api) introduces a couple problems:

  • How do we keep the resolver logic from being executed multiple times if it’s initial logic hasn’t been completed yet (very problematic if we’re making network requests)?
  • How do we signal to client code that the resolver logic has finished?

In order to solve these problems, wp.data automatically enhances every registered store that registers controls and resolvers with a reducer, and a set of selectors and actions for resolution state. This enhancement allows wp.data to be aware of the resolution state of any registered selectors with attached resolvers.

The resolution state keeps track by storing a map of selector name and selector args to a boolean. What this means is that each resolution state is tied not only to what selector was invoked, but also what args it was invoked with. The arguments in the map are matched via argument order, matching primitive values, and equivalent (deeply equal) object and array keys (if you’re interested in the specifics, the map is implemented using EquivalentKeyMap).

With this enhanced state (which is stored in your store state on a metadata index), you have the following selectors (for usage examples, we’ll use our getProducts selector as an example) :

SelectorDescriptionUsage
getIsResolvingReturns the raw isResolving value for the given selector and arguments. If undefined, that means the selector has never been resolved for the given set of arguments. If true, it means the resolution has started. If false it means the resolution has finished.wp.data.select( ‘data/products’ ).getIsResolving( ‘getProducts’ );
hasStartedResolutionReturns true if resolution has already been triggered for a given selector and arguments. Note, this will return true regardless of whether the resolver is finished or not. It only cares that there is a resolution state (i.e. not undefined) for the given selector and arguments.wp.data.select( ‘data/products’ ).hasStartedResolution( ‘getProducts’ );
hasFinishedResolutionReturns true if resolution has completed for a given selector and argumentswp.data.select( ‘data/products’ ).hasFinishedResolution( ‘getProducts’ );
isResolvingReturns true if resolution has been triggered but has not yet completed for a given selector and arguments.wp.data.select( ‘data/products’ ).isResolving( ‘getProducts’ );
getCachedResolversReturns the collection of cached resolvers.wp.data.select( ‘data/products’ ).getCachedResolvers();

You will mostly interact with resolution state selectors to help with determining whether an api request within a resolver is still resolving (useful for setting “loading” indicators). The enhanced resolution logic on your store will also include action creators, but typically you won’t need to interact with these much as they are mostly used internally by wp.data to track resolution state. However, the resolution invalidation actions here can be very useful if you want to invalidate the resolution state so that the resolver for the selector is invoked again. This can be useful when you want to keep the state fresh with data that might have changed on the server.

As with the selectors, the action creators receive two arguments, the first is the name of the selector you are setting the resolution state for, and the second is the arguments used for the selector call.

Action CreatorDescriptionExample Usage
startResolutionReturns an action object used in signalling that selector resolution has started.wp.data.dispatch( ‘data/products’ ).startResolution( ‘getProducts’ );
finishResolutionReturns an action object used in signalling that selector resolution has completed.wp.data.dispatch( ‘data/products’ ).finishResolution( ‘getProducts’ );
invalidateResolutionReturns an action object used in signalling that the resolution cache should be invalidated for the given selector and argswp.data.dispatch( ‘data/products’ ).invalidateResolution( ‘getProducts’ );
invalidateResolutionForStoreReturns an action object used in signalling that the resolution cache should be invalidated for the entire store.wp.data.dispatch( ‘data/products’ ).invalidateResolutionForStore();
invalidateResolutionForStoreSelectorReturns an action object used in signalling that the resolution cache should be invalidated for the selector (including all caches that might be for different argument combinations).wp.data.dispatch( ‘data/products’ ).invalidateResolutionForStoreSelector( ‘getProducts’ );

Now that we know about the resolution state, let’s return to the flowchart. We left off at the point where wp.data has determined there’s a related resolver for the given selector and is determining whether resolution has started yet or not. Based on what we just looked at, you should know how it does this. Right! It’s going to call a resolution state selector. The specific selector called here is hasStartedResolution. If that returns true, then wp.data will basically abort (because the resolver is already running, or completed asynchronously).

If hasStartedResolution( 'getProducts' ) returns false however, then wp.data will immediately dispatch the startResolution action for the selector. Then the getProducts resolver is stepped through.  Now remember getProducts is a generator. So internally, wp.data will step through each yield it finds in the resolver. Resolvers and controls are expected to only yield action objects or return undefined.

The first value yielded from the resolver is the fetch control action. Since wp.data recognizes that this action type is for a control it then proceeds to invoke the control.  Recognizing that the control returns a promise, it awaits the resolution of the promise and returns the resolved value from the promise via calling the generators generator.next( promiseResult ) function with the result.  This assigns the value to the products variable and the generator function continues execution. If there are no products, then the resolver returns undefined and this signals to the resolver routine that the resolver is done so wp.data will dispatch the finishResolution action for the selector.

If there are products, then the resolver returns the hydrate action. This triggers the dispatching of the hydrate action by wp.data and the dispatching of the finishResolution action because returning from a generator signals it is done. When the hydrate action is processed by the reducer (which we haven’t got to yet), this will change the state, triggering subscribers to the store, which in turn will trigger any selects in the subscriber callback. If the call to the getProducts was in subscribed listener callback, it will then get invoked and the latest value for getProducts (which was just added to the state) will get returned.

That in a nutshell (a pretty big nutshell at that), is how resolvers work!

Sidenote: if you want to dig into the technical logic powering this, check out the @wordpress/redux-routine package. This is implemented automatically as middleware in wp.data to power controls and resolvers, but can also be used directly as middleware for any redux based app.

WordPress Data Store Properties: Actions

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

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.

Read more “WordPress Data Store Properties: Actions”

WordPress Data Interlude: Let’s talk Apps

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

In the last post I gave you a quick overview of the api for registering a new data store. I mentioned that this next post would take a closer look at the individual properties of a store, but before we do that, let’s take a brief break and take a look at an app that we’ll use to illustrate and practice the various concepts and apis around wp.data. The app we’re going to work with does the following:

  • A view that displays products in a product directory. Each product has an add to cart button for adding the product to a cart.
  • A view that displays the contents of the cart, the total for the items the cart, and a way to remove items from the cart.
  • A view for adding, editing and deleting existing products (it might serve as the admin view eventually).

So, go ahead and play around with this app I’ll be here when you get back.

You back? Great! The code portion of the app is here. Go ahead and take a few minutes to look at the code.

Now that you’ve taken a look at the code, let’s think about the design of the app for a few minutes. 

Read more “WordPress Data Interlude: Let’s talk Apps”

WordPress Data: How to Register a Custom Store

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

The primary way in which you interact with state in wp.data is through a registered store. In this post we’re going to give an overview of the api for registering a store and then subsequent posts will dive into the specific components of the store config.

registerStore( reducerKey, options )

Every data store found in wp.data has to be registered and you can see many examples of this throughout the Gutenberg codebase. The interface via which you register a store with wp.data is via the registerStore function. Through this function you add your store to the centralized data registry.

Sidenote: Something important to mention here is that when you register a store through wp.data.registerStore, you are registering with the default registry. This has some implications that we’ll get into a later article. In nearly all cases, it’s okay to register with the default store.

The registerStore function accepts two arguments: a name to identify the module (or store), and an object with values describing  how your state is represented, mutated (or modified), and accessed. 

The first argument can be referred to by many different terms, “store namespace”, “store key”, “store name”, “module name”, “module namespace”, or even “reducer key”! To keep things simple, we’ll call this argument the store name. It must be unique and a common pattern for naming data stores is to do something like group/store_content.  The group portion of the store name here might be your plugin name, or application name etc. The store_content portion of the store name here might be what kind of data is kept in the store.

Read more “WordPress Data: How to Register a Custom Store”

WordPress Data: Interfacing With the Store

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

Now that we have our new custom data store registered, we need to start integrating it with our example app. There are numerous ways in which you can interface with the data store in your components but I’m going to cover the four most common, straightforward ways.

withSelect( mapToProps )( Component)

withSelect is a higher order component that you use to connect your custom wp.data store with a component.

The argument you feed the curried function is a callback that maps values from selected state in wp.data to props that will then be fed through to the wrapped component. This callback you provide will receive two arguments: the first is select which is an interface to the wp.data registered stores, and ownProps which is the props that are passed into the component from parent components in the tree.

Here’s an example of withSelect being used:

Read more “WordPress Data: Interfacing With the Store”

WordPress Data: Putting it all together and registering your store!

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

To recap: so far in the series we’ve looked at the various properties of the configuration object for a store:

  • selectors: which interface with the store for retrieving data from its state.
  • actions: (actually action creators) which are used to dispatch action objects for handling by the reducer.
  • reducer: which receives dispatched actions and determines what happens to the state as a result.
  • controls: allowing for asynchronous side-effects on action generators or resolvers.
  • resolvers: linked to selectors, resolvers allow for automatically resolving data for the initial slice of state the selector is retrieving for.

Read more “WordPress Data: Putting it all together and registering your store!”