Fantastic hooks and where to use them.

This post is an introduction to the useDispatch and useSelect hooks in the wp.data api found in the WordPress Gutenberg plugin. Note: the hooks will be available with the next release of the @wordpress/data package (which will happen after Gutenberg 5.9 is released)

So you’ve had a chance to finally figure out the wp.data api. You’ve written a few components using withSelect or withDispatch. You think you’ve got a handle on some of the finer points of higher order components in Gutenberg (and realize that’s essentially what withSelect or withDispatch are!). But now you start seeing things like useDispatch or useSelect pop up in use. What the heck are these? Where did they come from? Is everything you learned useless?

To answer the last question and appease your worries. No. Everything you’ve learned in using withSelect and withDispatch is still awesome. You can keep using them and your Gutenberg blocks and apps will still keep on rocking.

However, I want to take this opportunity to introduce you to a couple new friends in the Gutenberg space: useSelect and useDispatch.

Where’d they come from?

It probably doesn’t come as a surprise to most people who have done any development integrating with Gutenberg that it is primarily built using React. A significant new pattern to emerge in the react ecosystem in the last year is something known as React Hooks. Not to be confused with the WordPress hook system, in the words of the React Docs, hooks:

…let you use state and other React features without writing a class.

Reactjs.org docs

Essentially react hooks allow you more direct access to the React API in functional components. There’s some great documentation and tutorials out there on react hooks – I highly recommend the following:

One of the really interesting things about the react hooks api is that it introduces the ability to write custom hooks encapsulating common behaviour to use across multiple functional components. Essentially this is what useSelect and useDispatch are, custom hooks encapsulating logic for getting and setting data (or dispatching actions) in registered wp.data stores.

Where to use them

from withSelect to useSelect

To refresh our memory, withSelect is a higher order component you use to wrap a component so you can inject state driven props into it using registered selectors in a wp.data store.

So for example, let’s say you’ve created an app that displays some products you have and their prices. In this app, you’d probably have some sort of ProductWithPrice component that displays the price.

const ProductWithPrice = ( { product } ) => {
	return <div>
             <span className="product-name">{ product.name }</span>
             <span className="product-price">{ product.price }</span>
	</div>
};

This is great, and in most cases would work just fine if the provided product always has a static price associated with it. But let’s imagine for a moment that the store using this app has prices that are more dynamic because they depend on either the location of the customer using the app, or the number of other items in the cart etc. So you decide to move the price for products to a custom data store. Now you might do something like this:

const { select } = wp.data;

const ProductWithPrice = ( { product } ) => {
	const price = select( 'inventory' ).getPrice( product.name );
	return <div>
            <span className="product-name">{ product.name }</span>
            <span className="product-price">{ price }</span>
	</div>
};

On the surface this seems to make sense. However, there’s a few problems with it:

  • select is imported from the default registry, which may not necessarily be the registry in this components related tree. Why this is a potential issue is something for another article so I won’t expand on that here.
  • The price for the product will only get updated if the component remounts. If the product prop never changes, then the latest price will not be shown.
  • If the price is retrieved via a REST api call (as opposed to in app changes updating state), then the initial mount of the component will have a value of undefined for the product price.

Let’s attempt a fix for the last two issues. Using familiarity with react lifecycle methods and the wp.data.subscribe api, you might come up with something like this:

const { select, subscribe } = wp.data;
const { __ } = wp.i18n;
const { Component } = wp.element;

class ProductWithPrice extends Component {
	constructor( props ) {
		super( props );
		this.state = {
			price: __( '...retrieving' ),
		};
		this.unsubscribe = () => {};
	}

	updatePrice( price ) {
		this.setState( { price } );
	}

	componentDidMount() {
		this.unsubscribe = subscribe( () => {
			const newPrice = select( 'inventory' )
				.getPrice( this.props.product.name );
			if ( newPrice !== this.state.price ) {
				this.updatePrice( newPrice );
			}
		} );
	}

	componentWillUnmount() {
		this.unsubscribe();
	}

	render() {
		return <div>
			<span className="product-name">{ product.name }</span>
			<span className="product-price">{ this.state.price }</span>
		</div>;
	}
}

Oh wow, things just jumped up in complexity didn’t it? Yes, it did.

While it solves the issues with a stale price (price now get’s updated anytime it changes in the store state), and also handles if price is retrieved via REST api (via a selector with a resolver), it still doesn’t address the the tight coupling to the default registry (and now we have both select and subscribe being used from the default registry). Not only that, but what if we have other components that need the up-to-date price for a given product? Ugh, we’d have to repeat this logic in all those other components.

This is where withSelect came to the rescue. Here’s our ProductPrice component implemented using withSelect:

const { withSelect } = wp.data;
const { __ } = wp.i18n;

const ProductWithPrice = ( { product, price } ) => {
 return <div>
  <span className="product-name">{ product.name }</span>
  <span className="product-price">{ price }</span>
 </div>;
};

export default withSelect( ( select, { product } ) => {
 return {
  price: select( 'inventory' ).getPrice( product.name ) || __( '...retrieving' ),
 };
} )( ProductWithPrice );

Things got a bit less complex again didn’t they? withSelect essentially takes care of all the boilerplate you need to add (subscriptions, state change etc) you saw in the previous example and exposes just the things you declare via the mapSelectToProps callback you provided to withSelect. On top of that, withSelect will always expose the select function from the registry exposed in context with the component being used (eliminating the first problem we had in the first example).

So finally, we’ve got an api that makes it relatively easy to interact with the inventory store. Still, let’s take a look at what things look like when we implement the new useSelect hook!

const { useSelect } = wp.data;
const { __ } = wp.i18n;

const ProductWithPrice = ( { product } ) => {
	const { price = __( '...retrieving' ) } = useSelect( ( select ) => {
		return { price: select( 'inventory' ).getPrice( product.name ) };
	}, [ product.name ] );
	return <div>
		<span className="product-name">{ product.name }</span>
		<span className="product-price">{ price }</span>
	</div>
};

We’ve moved back towards the apparent simplicity of the original example didn’t we? One advantage of using hooks is that similar to rendering, it allows you to write code that declares what happens at all points of time simultaneously. Like withSelect, useSelect abstracts away the subscriptions to the store state so you don’t have to worry about setting the subscriber on component mount and unsubscribing when the component un-mounts. Where things differ, is you are able to keep the interaction with the store in context with the component using it.

Another advantage to using hooks is that you can compose them into your own custom hooks. You can do the same with higher order components too, but let’s take a look at the difference. Here’s a method of creating a common higher order component to provide to any component that needs a price for a given product.

const { withSelect } = wp.data;
const { createHigherOrderComponent } = wp.compose;
const { __ } = wp.i18n;

const withPrice = createHigherOrderComponent(
	withSelect( ( select, { product } ) => {
		return {
			price: select( 'inventory' ).getPrice( product.name ) || __( '...retrieving' ),
		};
	} ),
	'withPrice'
);

const ProductWithPrice = ( { product, price } ) => {
	return <div>
		<span className="product-name">{ product.name }</span>
		<span className="product-price">{ price }</span>
	</div>;
};

export default withPrice( ProductWithPrice );

In the above example, I’ve used the wp.compose.createHigherOrderComponent helper to create a higher order component that can now wrap any component to enhance it with a price state provided by the inventory store. Some caveats though are:

  • The component being enhanced must receive the price prop from withPrice.
  • Components are always wrapped by the higher order withPrice component. Generally this doesn’t matter too much, but it does increase the complexity of the component tree.

So what would things look like if I create a custom hook implementing useSelect?

const { useSelect } = wp.data;
const { __ } = wp.i18n;

export const usePrice = ( product ) => {
	const { price = __( '...retrieving' ) } = useSelect( ( select ) => {
		return { price: select( 'inventory' ).getPrice( product.name ) };
	}, [ product.name ] );
	return price;
};

const ProductWithPrice = ( { product } ) => {
	const price = usePrice( product );
	return <div>
		<span className="product-name">{ product.name }</span>
		<span className="product-price">{ price }</span>
	</div>;
};

What would you prefer to do? I would prefer the latter, here’s why:

  • usePrice can be used declaratively in any component that receives a product prop.
  • It’s easier to reason about where the price is coming from because it’s reference is directly in context with it’s implementation.

Still, I realize there may be some people that look at this and think:

meh, not much difference really

the astute programmer

Maybe if I start expanding on this example with useDispatch you’ll think differently… so let’s continue!

from withDispatch to useDispatch

The differences between using withDispatch and useDispatch are very similar to the progression we saw in the previous section with components using withSelect vs useSelect. So I’m not going to do the same progression here. Instead, let’s expand on our product price example a bit.

Let’s say the ProductWithPrice component now includes some user interface for seeing what the price would be with a special code they were given on a coupon (I realize this ux is probably not what you’d have in real life, but roll with me for now). So our component now has an input field for adding the code, and a submit button for checking the code. Let’s assume that our inventory store has an action creator for updating the price for a product using a given product name and coupon code. Using withDispatch, and withSelect to create a custom withPrice higher order component you might end up with something like this:

const { withDispatch, withSelect } = wp.data;
const { createHigherOrderComponent } = wp.compose;
const { __ } = wp.i18n;

export const withPrice = createHigherOrderComponent(
	withDispatch( ( dispatch, { product } ) => {
		return {
			setCode: ( code ) => dispatch( 'inventory' ).setCode( product.name, code ),
		};
	} ),
	withSelect( ( select, { product } ) => {
		return {
			price: select( 'inventory' ).getPrice( product.name ) || __( '...retrieving' )
		}
	} ),
	'withPrice'
);

class ProductWithPrice extends Component {
	constructor( props ) {
		super( props );
	}

	currentCode = '';

	updateCode = ( code ) => {
		this.currentCode = code;
	};

	onClick = () => {
		this.props.setCode( this.currentCode );
	};

	render() {
		return <div>
			<span className="product-name">{ product.name }</span>
			<span className="product-price">{ this.props.price }</span>
			<br/>
			<input type="text" onChange={ this.updateCode } />
			<button onClick={ this.onClick }>{ __( 'Submit Code' ) }</button>
		</div>
	}
}

export default withPrice( ProductWithPrice );

Now compare the above with an implementation of useSelect and useDispatch:

const { useSelect, useDispatch } = wp.data;
const { useRef, useCallback } = wp.element;
const { __ } = wp.i18n;

export const usePrice = ( { product } ) => {
	const { price = __( '...retrieving' ) } = useSelect( ( select ) => {
		return { price: select( 'inventory' ).getPrice( product.name ) };
	}, [ product.name ] );
	const { setCode } = useDispatch( 'inventory' );
	const currentCode = useRef( '' );
	const onCodeChange = useCallback( ( code ) => {
		currentCode.current = code;
	} );
	const updatePriceUsingCode = useCallback( () => {
		setCode( product.name, currentCode.current );
	}, [ product.name ] );
	return { price, onCodeChange, updatePriceUsingCode };
};

const ProductWithPrice = ( { product } ) => {
	const { 
		price,
		onCodeChange,
		updatePriceUsingCode
	} = usePrice( product );
	return <div>
		<span className="product-name">{ product.name }</span>
		<span className="product-price">{ price }</span>
		<input type="text" onChange={ onCodeChange } />
		<button onClick={ updatePriceUsingCode }>{ __( 'Submit Code' ) }</button>
	</div>
};

Granted, this example is likely something that wouldn’t be implemented quite as it is here in the real world. But it does demonstrate how the more declarative approach of hooks allows for a straightforward read of the logic.

I hope this little overview of two fantastic hooks and where to use them helped you out!

  1. Is it possible to do something like:

    function ProductsContainer() {
    const products = useSelect( select => {
    return select( "products_store" ).getProducts();
    }, [] );

    return (
    <Products
    candidates={ products }
    />
    );
    }

    Where getProducts gets all products asynchronously. I’m pretty sure I’m fetching and adding the data to the store correctly but products is not updated with the last value.

    If I change it to use withSelect instead it works correctly. I just don’t want to use withSelect for the same reasons you mentioned above “it does increase the complexity of the component tree”

Leave a Reply to asumaranCancel reply

Up Next:

Testing Javascript for your Gutenberg Integrated Plugin

Testing Javascript for your Gutenberg Integrated Plugin