Note, 2023: This article was most relevant when Redux initially launched its hooks API as people began migrating to hooks. While it probably isn’t as useful as it was then, it is still accurate in 2023 and relevant for anyone who is working with a legacy codebase. You might also be interested in our newer post: Redux Toolkit is the official way to write Redux apps.
React Redux recently released version 7.1, which includes long
awaited support for React Hooks. This means that you can now ditch the connect
higher-order component and use Redux with Hooks in your function components.
This post will take a look at how to get started using Redux with Hooks and then
explore some gotchas of this approach.
What are Hooks?
Hooks were added to React in 16.8 and allow you to access things like state, React lifecycle methods, and other goodies in function components that were previously only available in class components.
For example, a React class component like this:
class Count extends React.Component {
state = {
count: 0,
};
add = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.add}>Add</button>
</div>
);
}
}
Could be rewritten as a function component using Hooks like this:
const Count = () => {
// state variable, initialized to 0
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Add</button>
</div>
);
};
The code is more concise and allows teams to make more use of function components without having to convert them to class components as soon as they need state or access to the React lifecycle. This post isn’t really about Hooks in general, so I’d check out the excellent Hooks documentation if you’d like to learn more.
How to use Redux with Hooks
React Redux now includes its own useSelector
and useDispatch
Hooks that can
be used instead of connect
.
useSelector
is analogous to connect
’s mapStateToProps
. You pass it a
function that takes the Redux store state and returns the pieces of state you’re
interested in.
useDispatch
replaces connect
’s mapDispatchToProps
but is lighter weight.
All it does is return your store’s dispatch
method so you can manually
dispatch actions. I like this change, as binding action creators can
be a little confusing to newcomers to React Redux.
Alright, so now let’s convert a React component that formerly used connect
into one that uses Hooks.
Using connect
:
import React from "react";
import { connect } from "react-redux";
import { addCount } from "./store/counter/actions";
export const Count = ({ count, addCount }) => {
return (
<main>
<div>Count: {count}</div>
<button onClick={addCount}>Add to count</button>
</main>
);
};
const mapStateToProps = (state) => ({
count: state.counter.count,
});
const mapDispatchToProps = { addCount };
export default connect(mapStateToProps, mapDispatchToProps)(Count);
Now, with the new React Redux Hooks instead of connect
:
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { addCount } from "./store/counter/actions";
export const Count = () => {
const count = useSelector((state) => state.counter.count);
const dispatch = useDispatch();
return (
<main>
<div>Count: {count}</div>
<button onClick={() => dispatch(addCount())}>Add to count</button>
</main>
);
};
I like that using Redux with Hooks is a little bit simpler conceptually than
wrapping components in the connect
higher-order component. Another benefit of
not using the higher-order component is that you no longer get what I call
“Virtual DOM of death”:
I suggest reading the full Redux Hooks documentation for more information.
useSelector gotchas
useSelector
diverges from mapStateToProps
in one fairly big way: it uses
strict object reference equality (===
) to determine if components should
re-render instead of shallow object comparison. For example, in this snippet:
const { count, user } = useSelector((state) => ({
count: state.counter.count,
user: state.user,
}));
useSelector
is returning a different object literal each time it’s called.
When the store is updated, React Redux will run this selector, and since a new
object was returned, always determine that the component needs to re-render,
which isn’t what we want.
The simple rule to avoid this is to either call useSelector
once for each
value of your state that you need:
const count = useSelector((state) => state.counter.count);
const user = useSelector((state) => state.user);
or, when returning an object containing several values
from the store, explicitly tell useSelector
to use a shallow equality
comparison by passing the comparison method as the second argument:
import { shallowEqual, useSelector } from "react-redux";
const { count, user } = useSelector(
(state) => ({
count: state.counter.count,
user: state.user,
}),
shallowEqual
);
There is a section in the Redux Hooks documentation that covers this in more detail.
Redux with Hooks vs. Connect higher-order component
Hooks are great. They allow for using function components in ways that weren’t previously possible, and the community is clearly moving in the direction of using function components and hooks when possible. The React documentation states:
We intend for Hooks to cover all existing use cases for classes, but we will keep supporting class components for the foreseeable future.
While there is no need to race off and convert all your existing code, our
recommendation for new code is to generally prefer function components and Hooks
over using class-based components and higher-order components. Using the Redux
Hooks aligns with this recommendation and provides other key benefits
over connect
.
The main benefit of using the Redux Hooks is that they are conceptually simpler
than connect
. With connect
, you are wrapping your component and injecting
props into it. This can make it difficult to determine in the component which
props come from Redux and which are passed in. If you are using TypeScript,
correctly defining the types of connected components can be quite a chore for
this reason. The Redux Hooks, on the other hand, are just regular hooks that
don’t modify the public interface of your component. In TypeScript projects, I
create my own useSelector
that is typed to my store, and then I get
type-checking everywhere I use it. Easy!
React Hooks are a useful new feature, and React Redux’s addition of Redux-specific hooks is a great step toward simplifying Redux development.