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
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:
import { withSelect } from '@wordpress/data';
import { STORE_KEY } from '../data';
const MyComponent = ( props ) => {
return <div>{ props.content }</div>;
}
export default withSelect(
( select, ownProps ) => {
return {
content: select( STORE_KEY ).getContent( ownProps.type );
};
}
)( MyComponent );
Note, a few things about the implementation here:
select
is called with a given store key (remember every data store has a namespace) and then it returns all the selectors registered to that data store.- you can use incoming
ownProps
as arguments to the invoked selector. - you return an object and this object will be merged with the props fed to the wrapped component.
Some other things to remember about the mapping callback you provide withSelect
:
- This callback is automatically subscribed to state changes in any registered store in the store registry. So it’s completely normal to see this invoked numerous times in the lifecycle of a component.
- You can return
undefined
from your callback if there’s some condition where there are no props to inject.
It’s little known that the mapToProps
function provided to withSelect
will also receive a third argument which is the registry
. We’ll get more into what this registry object is in the next post of the series, but for now understand that this exposes the dispatch
function which allows you to also dispatch actions within your callback.
useSelect( mapToProps, dependencies )
useSelect
is the newer (and preferred) way of interacting with your data stores (and under the hood, withSelect
is now actually powered by useSelect
). It is similar in functionality to withSelect
in that you provide a mapping callback and return values, but it’s not a higher order component, rather, it is a custom React Hook.
The callback you provide to useSelect
will receive select
as the first argument and registry
as the second argument, so it is very similar to withSelect
, however any props you use will be bound from the scope of the initial function creation. This means, that if you are using props within the callback, you need to make sure you declare them as dependencies via the second argument passed to the hook so that React knows when to recreate memoized things using those values so any invocations are not using stale values. You can read more about how dependencies work here.
Here’s the same example we used for withSelect
converted to use useSelect
:
import { useSelect } from '@wordpress/data';
import { STORE_KEY } from '../data';
const MyComponent = ( props ) => {
const content = useSelect( ( select ) => {
return select( STORE_KEY ).getContent( props.type );
}, [ props.type ] );
return <div>{ content }</div>;
}
One major difference between the mapping callback you provide to useSelect
and withSelect
is that for useSelect
you can return whatever you want whereas from withSelect
it must be an object or undefined. It’s important to remember however that you must handle conditions where the selector might not have fully resolved yet (if it has a related resolver). So be aware of what stores you are selecting from and what the selectors might return.
withDispatch( mapPropsToDispatch )( Component )
Like withSelect
, withDispatch
is a higher order component that wraps whatever component you want to enhance with functions for dispatching actions to registered wp.data
stores. It too receives a mapPropsToDispatch
function with similar argument shape as withSelect
except the first argument is the registry dispatch
function which you use to dispatch actions from a specific store (or stores). The second argument is ownProps
and the third argument is the registry
instance which you can use to grab the select
function for retrieving some dynamic data from the store before the action is fired.
While withSelect
and withDispatch
are similar in some ways, it’s important to remember that withDispatch
requires the mapping callback to return an object that has functions as property values. It will throw console warnings if you don’t follow this rule.
Here’s an example of withDispatch
in use (building off of our withSelect
example):
import { withDispatch, withSelect } from '@wordpress/data';
import { compose } from '@wordpress/compose';
import { STORE_KEY } from '../data';
const MyComponent = ( props ) => {
return <div onClick={ props.clickedContent } >{props.content}</div>;
}
export default compose(
withSelect(
( select, ownProps ) => {
return {
content: select( STORE_KEY ).getContent( ownProps.type );
}
}
),
withDispatch(
( dispatch, ownProps ) => {
return {
clickedContent: ownProps.type === 'general' ?
dispatch( STORE_KEY ).clickedGeneralContent :
dispatch( STORE_KEY ).clickedContent
};
}
)
)( MyComponent );
Note, in this example, we’ve also utilized a special function from the @wordpress/compose
package. This function makes it easier to compose higher order components together to make the code easier to read. Without it, we would have had to write something like this:
export default withSelect(
( select, ownProps ) => {
return {
content: select( STORE_KEY ).getContent( ownProps.type );
}
} )(
withDispatch(
( dispatch, ownProps ) => {
return {
clickedContent: ownProps.type === 'general' ?
dispatch( STORE_KEY ).clickedGeneralContent :
dispatch( STORE_KEY ).clickedContent
};
}
)( MyComponent )
);
Which example do you find easier to read?
useDispatch( storeKey )
useDispatch
is probably the most straightforward out of all the interfaces we’ve looked at so far because it only receives one argument and that is simply the store you wish to retrieve actions from. Calling useDispatch
with a store key will return all the action creators registered to that store namespace as properties in an object.
Using the same example I’ve been demonstrating with in this post, here’s how we’d do things with useSelect
and useDispatch
.
import { useDispatch, useSelect } from '@wordpress/data';
import { STORE_KEY } from '../data';
const MyComponent = ( props ) => {
const content = useSelect( ( select ) => {
return select( STORE_KEY ).getContent( props.type );
}, [ props.type ] );
const { clickedContent, clickedGeneralContent } = useDispatch( STORE_KEY );
const onClick = props.type === 'general' ?
clickedGeneralContent :
clickedContent;
return <div onClick={ onClick }>{ content }</div>;
}
Notice that this is a much shorter block of code than the example used for withDispatch
. This is one of the reasons why useSelect
and useDispatch
are generally preferable to use over withSelect
and withDispatch
but there are still some cases where you might want to use the latter. For example, if you’re working with older code that is still using class components, it might be easier to implement withSelect
and withDispatch
initially then to refactor the component to a functional component.
One thing you can remember with useDispatch
is that the returned action functions will never change (unless the registry in the tree the component belongs to changes). So generally, while you shouldn’t need to include these as dependencies in hooks using them (similar to the setState
function from useState
), it’s still a good idea to.
Of course, these four interfaces are just the most common way to select data from registered wp.data stores and dispatch actions. There are others, but I’m not going to cover them here. You may also want to read another post I wrote, “Fantastic hooks and where to use them” in which I share a bit more on the benefits of using the hooks over the hocs (and some insight into the progression of developer quality of life between the low level wp.data interfaces all the way to the hooks when in a react context).
Finally, you now have enough knowledge through the series so far to convert the app you’ve been working on over to use wp.data instead of tree state for product data. It’d be good for you to try and implement the interfaces above in your fork of the app now. But if you are stuck, here’s the completed conversion of the app.