Gutenberg: Updating withSelect and withDispatch into React Hooks (useSelect and useDispatch)

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!

Leave a comment