Introducing: Redux HooksAggelos ArvanitakisBlockedUnblockFollowFollowingApr 24react-redux@7.
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.
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 that 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), 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 ArtilleryAlthough 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// oryarn add react-redux@nextRight now, the set of hooks 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 exampleOther 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.
useActions()A hook that binds action creators so that they dispatch actions when called.
This is the well-known mapDispatchToProps object/function, implemented in a hook.
This hook is useful when you don’t need to read the state, but only provide a component props that are going to fire redux-actions when called.
It accepts a single action creator, an object or an array of action creators.
The return value of the hook will be in the same form as the argument you have provided to it.
Thus, if you pass in a single action creator, the hook will return a single action creator.
If you pass in an object of functions, that’s what you’ll get back and so on…An important thing to note, is that the action creators returned from useActions() will always have new function references on every render, unless a second argument is given to the hook(exactly like useCallback).
If you provide an array of variables as the second argument, then only when a variable from the array changes, will the action creators will be recomputed and get a new reference.
If you provide an empty array, then the action creators will always have the same reference and will never be re-computed.
Thus, if your action creators are dependent on some props or variables, you should add them as the second argument to useActions() .
If the action creators are not dependent on any external props or variables, then add an empty array () as the second argument, in order to benefit from referential caching.
useActions exampleuseRedux()This hook is the combination of both useSelector() & useActions().
I know you wouldn’t have guessed it, but it’s the connect() HOC implemented in a hook.
The first argument is the same as what you would write in useSelector() and the second argument is the same what you would write in useActions().
There is a caveat though.
The useRedux() hook currently does not allow you to specify a dependency array argument, so the action creators that it creates, will be re-created every time the component renders (exactly like how useActions() would work if you didn’t add a second argument to it!).
This could potentially lead into performance issues for your app (If you wanna learn more as to why this may happen, I’d urge you to read my article about redux performance).
If you need consistent function references, then useRedux is not the way to go.
You should utilise useActions and take advantage of its second argument in order keep consistent references in your action creators.
useRedux exampleuseDispatch()A hook that exposes the store’s internal dispatch method.
This is perhaps not a very common need, but it might be helpful in cases where you want to expose the dispatch function to a 3rd-party module that lives outside the React ecosystem.
useDispatch exampleuseStore()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.
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 exampleThoughts & TakeawaysShould 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 explicitly specify dependencies (through the aforementioned second arguments) on each hook.
Secondly, if your code depends on ownProps within mapStateToProps , you may end up writing more code using redux hooks that you would otherwise write using the connect() HOC.
Regardless of that, 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.
If I created some confusion, 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.
Anything that can be written as a HOC can be re-written as a render-prop or a custom hook and vice versa.
My personal suggestion would be to 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.
If you liked this, consider following me on twitter and sharing the story with your developer friends ????.. More details