react-redux@7.1.0-alpha has just been released and with it a whole new set of hooks that could potentially change the way you write your redux in your React apps. In this article I will give you some insight in why it took that long to release, what the API is, and finally my thoughts on migrating to hooks.

Didn’t we already have that?

Although 3rd-party libraries already supported that, this feature has been long awaited by me, because all the existing solutions out there were not performance oriented. Up until now any existing library that allowed you to utilise things like useRedux or useConnect wasn’t able to solve a crucial performance related issue, that caused your hooked component to re-render any time any piece of state changed, regardless of whether your component listened to this particular piece of state or not. This is because the 6.x.x . version of react-redux attempted to fully utilise the new React Context API which unfortunately has no render bail-out techniques. What this means, is that anytime a Context.Provider updates, then every Context.Consumer would have to update as well. Thus, any component with a hook like useRedux or useConnect would always update regardless of whether it was listening to the state slice that got updated. This particular problem was the reason react-redux had to release a new major version (namely 7.x.x ), in which it re-wrote the code, decoupled itself from the previous Context API implementation and was ready now to support a shiny new set of hooks!

How will this affect my code?

Well, it depends. The new set of hooks allows you to connect your components with redux without the need for connect() HOCs anymore. Personally I believe this is a huge win for large-scale apps that utilise redux, because now you can actually create custom re-usable hooks without the need of additional container components. Up until now, React hooks were great, but you couldn’t actually create a hook that would read some state from redux or dispatch to it. Thus, custom hooks were perfect for react-only stuff, while they wouldn’t be a preference whenever the code had anything to do with redux.

The bonus of these new redux hooks is that you no longer have to maintain separate container/presentational files with mapStateToProps , mapDispatchToProps and connect , but you can instantly read the state right inside your functional component. In addition, you can integrate any existing custom hooks with redux and instead of passing the state data as an argument to the hook, the hook itself can now read it directly from the redux state!

The Artillery

Although the react-redux version that supports hooks is only in alpha at the moment of writing, you can still experiment with them on a non-production level.

To install it, simply run:

npm install react-redux@next // or yarn add react-redux@next

Right now, there is a set of 4 hooks which includes:

useSelector()

A hook that extracts values from the Redux store state and subscribes to the store. This is essentially similar to the mapStateToProps function implemented in a hook, but with some small differences.

Firstly, there are no ownProps available and one should use custom logic using useCallback or useMemo in order to get them.

Secondly, the selector function used to select state slices will not be cached, unless you provide a second argument to the useSelector() . This second argument is an array of values that whenever changed, the selector function will be re-computed. If your selector function is dependent on other variables besides the state , then you need to add these variables as the second argument to this hook in the form of an array. If your selector function only depends on state, then forget about the second argument of this hook. If it helps, this is exactly what you would do in useCallback , where the same function is re-used as long as the values passed in as a second argument don’t change.

useSelector example

Other than that, the same smart render-bail out techniques still apply and your component won’t re-render if the new mapped props are the same as old mapped props.

Because of the batching behaviour used in React Redux, a dispatched action that causes multiple useSelector() s in the same component to recompute their values, should only result in a single re-render of your “hooked” Component. Thus, rest assured that the number of useSelector() hooks in a component is unrelated to the number of re-renders that this component will have. In the example above we could have split the single useSelector() into 2 separate ones (one reading the title and the other reading the content) and it would be exactly the same in terms of performance & number of renders.

useDispatch()

A hook that exposes the store’s internal dispatch method. Whenever a component needs to fire a redux action, then this hook is what you’ll need. Unfortunately, there isn’t a mapDispatchToProps hook equivalent, so whenever you want to fire any action you’ll need to call its action creator using dispatch(myActionCreator()) . Firing an action like that may seem counterintuitive since you have been used to calling a prop that has already been wrapped with dispatch through your connect() HOC, but hooks are here to make things more idiomatic and bring additional clarity to your code.

Unfortunately, the bad thing is that now, whenever you want to fire an action as a response to a user event, you’ll have to create an anonymous function like so: () => dispatch(myActionCreator()) . Due to the nature of anonymous functions, this will get a new reference on every re-render. Thus if you passed it as a prop to another component, then the latter would never be able to benefit from render bail-out techniques ( shouldComponentUpdate , memo , PureComponent ). In order to achieve that, 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() .

useDispatch example

useStore()

A hook that returns the Redux store instance. This is useful whenever you are creating a library that needs access to the store instance defined by the consumer application (i.e. connected-react-router ) .It might also be useful — if for some reason — a component is using a different store than the rest of your components of if you are unit-testing a component that’s not being wrapped with a Provider .

useStore example

Thoughts & Takeaways

Should I convert my connect() HOCs to hooks? Well — again — it depends. First off, you lose a lot of the automatic referential caching that connect() provides. That causes you to lose any performance gains, unless you make heavy use of useCallback() (especially for the functions that are using dispatch ). Secondly, if your code depends on ownProps within mapStateToProps , you may end up writing more code using redux hooks than you would otherwise write using the connect() HOC. Thirdly, you lose the dependency injection that mapDispatchToProps provided with regards to action creators.

Sadly, there isn’t a single line migration from redux HOCs to redux hooks. You can’t simply swap out your single connect() HOC for a single “useConnect()” hook, cause there isn’t any. There used to be a useRedux() hook in the initial alpha release, but it was quickly scrapped as it didn’t give you the ability to properly memoize your action creators, something which led to performance issues (if you wanna know why, I’d urge you to read my article about redux performance). That means that every connect(mapStateToProps, mapDispatchToProps) HOC you currently have, would need to be replaced by 2 separate hooks, with potentially additional changes in your mapStateToProps if it utilises ownProps . Thus, the code you’ll have to write if you want to guard against potential performance issues, is more than what you would have written if you went the connect() way.

Regardless of all the above, what we typically do is have our dumb and smart layers separated intentionally. This allows us to both re-use the individual layers, as well as ease the testing process by addressing these two as different entities (if you wanna learn more about it, I would urge you to read my article on the importance of UI & State layers). Hooks, on the other hand, attempt to merge these two together, essentially creating a single entity from the smart & dumb layers. This is a new trend that started with the release of hooks back in 2018. The reasoning was that, most of the times, a presentational component was only related to a particular container component and essentially the re-usability of components was a non-issue. Testing, on the other hand, changed in order to address that, with a lot of people slowly migrating from unit & implementation testing (Enzyme) to integration & user-output testing (React testing library).

For these reasons, merging the dumb & smart layers could be either a win or a loss, depending on the way your app is architected and tested. While hooks may increase readability (no longer do you need to check in 2 places) and maintainability (with regards to file organisation & structure), some mature codebases may want to maintain the existing separation of layers, since it works better for them.

Just remember that at the end of the day, hooks are simply an alternative to HOCs or Render-Prop Components and most likely your company’s profit won’t depend on them. They are an implementation detail. Anything that can be written as a HOC can be re-written as a render-prop or a custom hook and vice versa. I’m not writing this article to diss on hooks, but to make people think a bit more before quickly jumping on the hook-refactoring train. My personal suggestion for the new redux hooks is the following:

Opt for them inside your custom hooks, but only use them inside a component when the connection logic is simple and performance is a non-issue.

If that’s too restricting, simply ignore it and always opt for hooks, but if it becomes cumbersome in any way, don’t be afraid to switch to a non-hook implementation.

Thanks for reading :)

P.S. 👋 Hi, I’m Aggelos! If you liked this, consider following me on twitter and sharing the story with your friends 😀