This post is a quick introduction into a way to keep your your Gutenberg code up to current standards by using React hooks. We’ll see how this is beneficial, why we should do it, and how.
Huh, hooks?
I assume you already have some experience working with a bit more complex Gutenberg blocks or plugins that queries posts, or fetches and updates post meta. And thus been working with withSelect
and/or withDispatch
. These are higher-order-components in WordPress that allows you to access WordPress’ stores in order to fetch or push information that’s a bit outside “normal” block or post editing. You’ve possibly also been forced to use compose
in order to combine your component with multiple higher-order components. If your code is using these patterns, don’t worry – they work perfectly fine, and will continue to work! But as technology advanced, we can do better.
Early in 2019, React launched hooks (version 16.8) – which let you use state without using a class and improve other features which gives more legible and reusable code. For example removing the necessity to wrap your code into higher-order-components in order to access registries. And in summer 2019 WordPress followed, launching custom hooks: useDispatch
and useSelect
.
The benefits of hooks are many. The most common example is allowing to use component state without having to write a Javascript class (useState
). But in my opinion the biggest benefit is reusability. By removing the necessity to use patterns like render props and higher-order components the components can be written into far more independent pieces. Another benefit of hooks is that it makes your code easier to read and understand!
Let’s jump right into some examples and see how to implement useSelect
and useDispatch
hooks in our code!
Implementing useSelect
Let’s begin with withSelect
and its corresponding hook useSelect
. These are used to get state-derived props from registered selectors. Common examples are accessing the ‘core
‘ or ‘core/editor
‘ selectors in order to perform queries for posts (getEntityRecords
), access post meta (getEditedPostAttribute
) or simply getting current post type or other information.
For demonstration purposes I will start with a simple example. Assume I have a block component where I have a post selector somewhere. In order to populate post choices I need to access the registry ‘core
‘ and perform a GetEntityRecords()
call.
Old way using withSelect
If I want to query posts in a block I would need to wrap my component in withSelect
. This is commonly done in the export
statement.
Please note that this code example is not complete as a real functional block or JS plugin, it’s been stripped out to just show the core concepts of withSelect
. If you are looking for practical code examples I have other articles that cover this: e.g. how to implement blocks with a post select, or how to add post meta to the inspector. Those are written with withSelect
and withDispatch
, so do those, and then come back here to learn how to transform them into hooks.
const { withSelect } = wp.data; const AWP_Example_Plugin = ( { somePosts } ) => { console.log("Posts from withSelect = ", somePosts); return( <div> ... </div> ); } export default withSelect( ( select ) => { return { somePosts: select( 'core' ).getEntityRecords( 'postType', 'page' ), }; } ) ( AWP_Example_Plugin );
As you can see in line #12-16
I wrap my component with withSelect
. Inside the withSelect function I can access the store I want, and I return a post query in the prop “somePosts
“. This prop will then be available inside my AWP_Example_Plugin
component (as you can see I destructure somePosts
from the props at line #3
).
As mentioned before, this method works just fine – and will continue to do so. But there are several downsides with this. One is that this is not very easy to understand. Sure, this was a very simple example. At first glance at the component itself you are not sure where the prop somePosts
come from or what it is. You would have to know to look for the export statement and see if there are any wrappers. It also feels a bit disjointed. You do quite a share of important work outside your component, for something that you actually want available inside your component.
Let’s do it better using hooks.
Converting into useSelect
Converting a withSelect
into useSelect
is as simple as it gets. The first step is that we can define the variable somePosts
inside our component, rather than on the outside by the export
statement. The second step is moving the whole function we had in withSelect
into useSelect
. And that’s it.
const { useSelect } = wp.data; const AWP_Example_Plugin = () => { const { somePosts } = useSelect( ( select ) => { return { somePosts: select( 'core' ).getEntityRecords( 'postType', 'page' ), }; } ); console.log("Posts from useSelect = ", somePosts); return( <div> ... </div> ); } export default AWP_Example_Plugin;
The above code provides the exact same result as the one with withSelect
. The difference is that the code is now much more understandable! We can now very easily see that the variable somePosts
is storing the result of a post query, right inside our component.
Important note: useSelect
accepts a second parameter; an array of dependencies. The dependencies are variables that make sure we only run useSelect
when one of the dependencies (variable values) have changed. This bit is more important in useDispatch
than here, so I will explain this bit further in the useDispatch
section below.
Implementing useDispatch
Now let’s look at withDispatch
and its corresponding hook useDispatch
. They are used to dispatch props to registries. For example telling Gutenberg to update a post meta via ‘core/editor
‘.
Again, for demonstration purposes my code examples are not complete functional pieces of code – they illustrate just the parts concering these patterns. In this example I assume I have a textfield showing a post meta – and I wish to update the post meta value on change.
Old way using withDispatch
As withDispatch
is a higher-order component, I would need to wrap my component inside this. This is commonly done in the export
statement. In order to make the textfield for our meta work we
For example:
const { withDispatch } = wp.data; const { __ } = wp.i18n; const { PluginDocumentSettingPanel } = wp.editPost; const { TextControl, PanelRow } = wp.components; const AWP_Example_Plugin = ( { setPostMeta } ) => { return( <PluginDocumentSettingPanel title={ __( 'withDispatch Example', 'txtdomain') }> <PanelRow> <TextControl label={ __( 'Example post meta', 'txtdomain' ) } value="" onChange={ ( value ) => setPostMeta( { example_post_meta: value } ) } /> </PanelRow> </PluginDocumentSettingPanel> ); } export default withDispatch( ( dispatch ) => { return { setPostMeta( newMeta ) { dispatch( 'core/editor' ).editPost( { meta: newMeta } ); } }; } )( AWP_Example_Plugin );
At line #20-26
we wrap the component inside withDispatch
, in which we return a function “setPostMeta
” that dispatches post meta (to update it). At line #6
we destructure the prop so that we can use it in the text field’s onChange
event at line #13
. (Please note that the above example would not be functional because we set the value of the textfield to an empty string. This is just to demonstrate how we would use dispatch).
Again we can see that the code is not as straightforward to understand. You would have to know to look for the wrapper in order to figure out what the prop “setPostMeta
” is or come from. Let’s do it better using hooks!
Converting into useDispatch
Changing withDispatch
into useDispatch
is not quite as straightforwards as it is converting withSelect
into useSelect
. It is still pretty easy though. There are two things to keep in mind. One is that useDispatch
takes a store name as first parameter. We would then access the store and call its available functions when we use it (e.g. in a text field’s onChange
event). Secondly the array of dependencies to useDispatch
‘s second parameter is more important then with useSelect
. You can risk your code ending up in a forever loop if you don’t manage dependencies correctly. Only when the dependency variables are changed, the script will re-run useDispatch
.
For example:
const { useDispatch } = wp.data; const { __ } = wp.i18n; const { PluginDocumentSettingPanel } = wp.editPost; const { TextControl, PanelRow } = wp.components; const AWP_Example_Plugin = () => { const tmpMetaValue = ""; const { editPost } = useDispatch( 'core/editor', [ tmpMetaValue ] ); return( <PluginDocumentSettingPanel title={ __( 'useDispatch Example', 'txtdomain') }> <PanelRow> <TextControl label={ __( 'Example post meta', 'txtdomain' ) } value={ tmpMetaValue } onChange={ ( value ) => editPost( { meta: { example_post_meta: value } } ) } /> </PanelRow> </PluginDocumentSettingPanel> ); } export default AWP_Example_Plugin;
At line #8
we destructure what we need from the store ‘core/editor
‘. We are only interested in the editPost
function which we can use to update post meta. As second parameter to useDispatch
we provide dependencies. As for an example of updating post meta, using the value of the post meta (by using useSelect
) would be perfect as dependency. In this example I’ve just set it as an dummy variable. Look further down for a more complete and realistic example. And then at line #16
in the text field’s onChange
event, we call editPost
to update the meta. Note the difference in this line with using withDispatch
above! Since we use editPost
directly instead of returning a function to update post meta for us (setPostMeta
), we need to provide an object with meta
as key and then an object with the post meta we want to update. We have kind of split the withDispatch
code in pieces.
Still, using useDispatch
makes the code much more readable and understandable. No more code added outside our component that we need to use inside.
A more complete example & eliminating the need for compose
Chances are if you are using withDispatch
, you will need withSelect
in that component as well. In the examples for converting into useDispatch
above we are updating a post meta value. But in order for the text field to work properly (and also show the current value), we would also need to fetch the post meta value.
If you need to use multiple higher-order components wrapped around your component, you would most likely use yet another Gutenberg function; compose
. It’s a handy function to combine multiple higher-order components into one higher-order component that you can wrap your component around.
Let’s look at a more complete example of a component that fetches a post meta value in a text field and updates when its value is changed. We begin with how we would have to go about it using withSelect
and withDispatch
(and thus also compose
).
Using withSelect
, withDispatch
and compose
const { __ } = wp.i18n; const { compose } = wp.compose; const { withSelect, withDispatch } = wp.data; const { PluginDocumentSettingPanel } = wp.editPost; const { TextControl, PanelRow } = wp.components; const AWP_Example_Plugin = ( { postMeta, setPostMeta } ) => { return( <PluginDocumentSettingPanel title={ __( 'withSelect and withDispatch Example', 'txtdomain') }> <PanelRow> <TextControl label={ __( 'Example post meta', 'txtdomain' ) } value={ postMeta.example_post_meta } onChange={ ( value ) => setPostMeta( { example_post_meta: value } ) } /> </PanelRow> </PluginDocumentSettingPanel> ); } export default compose( [ withSelect( ( select ) => { return { postMeta: select( 'core/editor' ).getEditedPostAttribute( 'meta' ), }; } ), withDispatch( ( dispatch ) => { return { setPostMeta( newMeta ) { dispatch( 'core/editor' ).editPost( { meta: newMeta } ); } }; } ) ] )( AWP_Example_Plugin );
At line #21-34
we compose withSelect
and withDispatch
and wrap them around our component. postMeta
from withSelect
and setPostMeta
from withDispatch
are now available in our component as props. We destructure them at line #7
and use them in #13
and #14
.
As you can see the necessary code outside our component is longer than the component itself. I don’t know about you, but for me it feels a bit disjointed. Developers may start reading this code from the top, not seeing the bottom part, and start to wonder where postMeta
and setPostMeta
come from – they seem to be magically available. For me this is very unintuitive.
Let’s look at how we would convert the above example into hooks.
Using useSelect
and useDispatch
const { __ } = wp.i18n; const { useSelect, useDispatch } = wp.data; const { PluginDocumentSettingPanel } = wp.editPost; const { TextControl, PanelRow } = wp.components; const AWP_Example_Plugin = () => { const { postMeta } = useSelect( ( select ) => { return { postMeta: select( 'core/editor' ).getEditedPostAttribute( 'meta' ), }; } ); const { editPost } = useDispatch( 'core/editor', [ postMeta.example_post_meta ] ); return( <PluginDocumentSettingPanel title={ __( 'useSelect and useDispatch Example', 'txtdomain') }> <PanelRow> <TextControl label={ __( 'Example post meta', 'txtdomain' ) } value={ postMeta.example_post_meta } onChange={ ( value ) => editPost( { meta: { example_post_meta: value } } ) } /> </PanelRow> </PluginDocumentSettingPanel> ); } export default AWP_Example_Plugin;
How beautiful and legible is that?! We can see straightaway in our component where postMeta
is fetched from and how we get access to editPost
. Our component also got a whole lot easier to re-use.
Note that in useDispatch
at line #12
we add the postmeta we are updating as dependency (postMeta.example_post_meta
). If you were to keep multiple post meta variables up to date in this component, you would have to add each as a dependency to ensure that dispatch runs (actually saving the post meta) when the value of one of them changes.
I hope this has been informative and helpful for someone out there. I am still a bit unfamiliar with hooks, but seeing the benefits of using them I am not turning back!