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!

Series NavigationWordPress Data: Interfacing With the Store
    1. Thanks! I don’t have the debugging tools and testing tips posts written yet 🙂 I got too busy near the end of the series and was unable to finish it off. It’s still on my “things to write” list but no eta when I’ll get it done.

Leave a Reply

Up Next:

WordPress Data Store Properties: Action Creator Generators

WordPress Data Store Properties: Action Creator Generators