Redux Hooks!

So it has been a hot reloading minute since React released their hooks API and I think it is fair to say that the majority of the community have played around with the new API. If you haven’t already, I definitely recommend it as they are great.

With Hooks being all the rage, a lot of popular libraries have jumped on board and released their own hooks as alternatives to their current methods. Some noteworthy examples include mobX, react-spring, formik, final-form just to name a few.

Redux, probably the most popular state management library for React, is no exception. Having recently released their new Hook’s API in v7.1.0 as an alternative to the connect() Higher Order Component(HOC) I was eager to have a play and fish around at what the drawbacks and benefits may be!

Basically, these new API’s allow you to subscribe to the Redux store and dispatch actions, without having to wrap your components in connect()

These new hooks are:

  1. useSelector
  2. useDispatch
  3. useStore

In this article we will be building:

  • A shopping cart (because it has never been done before…) using both the traditional connect HOC and the new Redux hooks.
  • A summary of the shopping cart with Redux Hooks and the Reselect Library.
  • The GitHub repository for the code can be found here

The purpose of this article is to show you:

  1. The traditional way React Components connect to the redux store.
  2. How we can connect to the redux store using the new Redux hooks.
  3. The benefits and drawbacks of using these hooks.

Please note, this article is not here to say which approach is better, but to highlight the differences between both and to help you get a better mental model of how these new hooks will work.

Project Setup

I set up the project using create-react-app with the typescript flag enabled. If you don’t want to code in typescript you are more than welcome to ignore the flag.

yarn create react-app my-store --typescript

I then added the following libraries as they are the key ones we will be focusing on.

yarn i redux reselect

File Structure

The file structure will look something like this

Setting up Redux Store and Product Reducers

Now let’s get on creating our Redux store and Product Reducers. I’ll assume readers will have an understanding of how Redux work so I won’t go into too much detail.

Please don’t get caught up in the details here, I just want you to know what our store will look like and the data our components are consuming.

In the store folder we will add an index.ts file.

In the reducer folder we will add 4 files.

  1. index.ts
  2. products.ts
  3. productsOld.ts
  4. storeHours.ts

The products.ts file will be the reducer for our Hooks ProductList component and the productsOld.ts file will be the reducer for our connect HOC ProductList component.

Show me the Redux Hooks!!

Almost there! We first need to create our ParentContainer.tsx file which will render both the ProductListHooks.tsx file and ProductListContainer.tsx file.

Our ParentContainer.tsx file will just simply render the ProductList Hooks and ProductListContainer as well as the open and close status.

The site currently looks something like this.

Some real solid UX/UI work here

Now the ProductListContainer.tsx will connect to the Redux Store using the connect HOC. As you can see from the snippet below there is nothing complicated going on here. We are simply giving the component state from the redux store and access to dispatch. This component will take the products from the store, map over them and pass it to the ProductItem.tsx component which will then render the data.

Redux Hooks!!

Now let’s show what the above component would look like if we used the new Redux hooks.

Ooo that is a little different isn’t it! In the file above we can see that we have two new hooks called useSelector and useDispatch.

But before I talk about them, you can see that the component is awfully similar the the ProductListContainer.tsx component before. It just takes the products from the store, maps over them and pass it to the ProductItem.tsx along with dispatch.

Now the juicy stuff, let’s break down the two new hooks shall we.

useSelector

This hook allows us to extract data from the Redux store state, using a selector function. The selector function will be called with the entire Redux store state as its’ only argument and has to return the relevant part of the state used by the component.

In the code snippet above we can see that it is returning the products array from the store.

Now, although it does the same job as mapStateToProps there are some key differences we must consider.

  1. The selector can return any value as a result, not just an object.
  2. When an action is dispatched, the useSelector hook will do a reference check and compare the previous value and the current result value. If they are different, the component will be forced to re-render. If they are the same the component will not re-render.

Now, I really want to discuss point 2 because this is where the biggest difference between connect and useSelector is. In Redux, connect uses shallow equality checks on the result of mapState calls to determine if re-rendering is needed. However, useSelector uses a strict === reference and this has several implications on how we should implement useSelector.

But what does that actually mean?!

Well let’s take a closer look at the ParentContainer.tsx file that is responsible for rendering the ProductListHooks and ProductListContainer. You can see that ParentContainer also displays whether the store is open or closed by dispatching an action to the store.

Using the awesome React Profiler in the devtools we can see what gets re-rendered if we click on the button a bunch of times.

Total Renders for ProductListHooks
Total renders for ProductListContainer

As you can see that is a huge difference! The button was clicked 10 times and the ProductListHooks re-rendered a total of 11 times whereas the ProductListContainer using connect only rendered one time!

Unlike connect the useSelector() hook does not prevent the component from re-rendering due to its parent re-rendering, even if the component's props did not change.

With mapState, all individual fields are returned in a combined object. It doesn't matter if the return object was a new reference or not - connect() just compared the individual fields. With useSelector(), returning a new object every time will always force a re-render by default.

How do we solve this problem?

Well, the simplest solution in this case is to wrap the ProductListHooks component in React.memo. This allows functional components to bail out on rendering if the props have not changed. This is much like a PureComponent or shouldComponentUpdate.

So let’s look at what our ProductListHooks.tsx component would look like if we used React.memo.

Well that was easy! And now have a look at the results, that’s just one total render.

The other way is that useSelector also takes a second argument, being an equality function. If the equality function returns false, the component will be forced to re-render, otherwise the component will not re-render. I am still working this one out and will readjust this post once I feel I have a more concrete example for you.

useDispatch

Now this hook simply returns a reference to the dispatch function from the Redux store and is used to dispatch actions as required. Now there are some gotcha’s here that are really important to be aware of.

If you look at the ProductListHooks.tsx component you can see that we are passing dispatch to the child ProductItem component. Let’s take a closer look at the ProductItem component.

Now let’s see what happens in our React Profiler if we click on the ‘Buy One’ button for just one of the items.

Renders for ProductItem

You can see that instead of just re-rendering the ProductItem component for the item that was selected, the component re-rendered all the ProductItems components even if that item was not selected.

So what do we do? We don’t want to re-render all the things :(

Unfortunately, whenever you want to fire an action as a response to a user event, you’ll have to create an anonymous function for example : () => dispatch({type: x, payload: y}). Why is this important? Because with every re-render, due to the nature of anonymous functions, a new reference to that function will be created.

When passing a callback using dispatch to a child component, it is recommended to memoize it with useCallback, if not the child would never benefit from the render bail out techniques like shouldComponentUpdate, memo , PureComponent.

In order to achieve this, you’ll have to make the function have the same reference, which in the hooks world translates into wrapping the entire anonymous function in a useCallback().

Our ProductListHooks container will now look something like the snippet below. You can see that we have wrapped the dispatch function in a useCallback hook.

The useCallback hook will return a memoized version of the callback that only changes if one of the dependencies has changed, in this case being dispatch.

We are now able to take advantage of the render bailout technique memo. Having a look at the ProductItem component we now get

Now let’s see what the React Profiler shows us.

Much better!

Isn’t that lovely, isn’t that wonderful! The MacBook Pro was the only ProductItem to get re-rendered as it was the only item that was selected.

Finally, I’d rather have a custom hook for useDispatch that I can reuse easily throughout my app. In the hooks folder I’ll create the custom hook called useDispatchToStore.

The useDispatchToStore is just a curried function that will take an action type and return a function that will take the payload and dispatch it to the store.

Our ProductListHooks component will now look like the snippet below. As you can see the only difference is that in the useCallback we make the dependency an action type, rather than the dispatch function.

In summary, with the useDispatch hook you should follow the following rules

  1. If passing dispatch down to a child, please wrap it in a useCallback.
  2. Memoize the child component.

Pro’s and Con’s of using Redux Hooks

PRO’S

  • No more connect HOC’s so less nodes in our component hierarchy.
  • Could abstract our selectors for more robust and reusable code.
  • More readable in a sense.

CON’S

  • You lose a lot of the automatic referential caching that connect()provides.
  • You lose access to the ownProps within mapStateToProps.

So should I start using the Redux Hooks?

Well….that depends. When you move away from connect you lose a lot of the performance benefits it provides. This means that you’ll have to be more cautious when considering re-renders and passing data from smart and dumb components. Currently I don’t think I’ll be moving away from connect but it was definitely fun to play with.

My advice is, when you start to use these hooks always ask yourself:

  • Is this better than using the current methods?
  • What am I losing / gaining by using these hooks

Always remember, that these hooks are just an alternative to the current methods. Anything you can write with HOC’s or Render props can also be written with hooks and vice versa.

Thanks for reading! If you enjoyed this article, feel free to hit that clap button a few times ( 👏👏👏👏) to help others find it.

P.S. If there is enough demand, I’ll write a Part 2 using these redux hooks with memoization libraries like reselect :)

Also, if you like what you read, feel free to have a look at my other article on How to migrate from Recompose to React Hooks here