It's been almost two years since new hooks have arrived into react-redux
and I've had enough chance to work on multiple long-running projects that uses both the old and new approaches to connect with Redux. Here is my conclusion for short: connect
is better that useSelector
. Here is why:
In version 7.1.1, react-redux
introduced their hooks API and updated their website with a tip that endorses hooks over the old higher-order component
approach:
We recommend using the React-Redux hooks API as the default approach in your React components.
The existing connect API still works and will continue to be supported, but the hooks API is simpler and works better with TypeScript.
React hooks made a great success since it's first introduction. It enables us to write tidy and understandable logic blocks. Yet, it doesn't mean that it is a one-size-fits-all solution for every use-case.
Ease of use
I must admit that hooks are easier to grasp and use than connected components. Using connect
requires more boilerplate code. It also requires to know concepts like higher-order components, bindActionCreators
, etc. to understand how it actually works. On the other hand, knowing how hooks work is enough to understand useSelector
and useDispatch
correctly. However ease of use is not always the most important think that we consider while choosing one approach over another.
Redux is really powerful when it comes to managing application state. But with great power comes great responsibility. We should give importance to how state is structured and be picky about what should be included into it and what is not. Only the data that is needed to be long-lived and globally available should make into Redux state. From this perspective, ease of use becomes our enemy. As React's useState
hook and Redux's useSelector
hook offer similar API surfaces, developers tend to put most of the state to their Redux state instead of picking only necessary ones. In the long run, it becomes bloated and structured by what components need rather than data itself.
Consider following example:
const SomeComponent = () => {
const dispatch = useDispatch();
return (
<div>
{/* Component content goes here */}
<button onClick={() => dispatch(openConfirmDialog())}>Click Me!</button>
</div>
);
};
const ConfirmDialog = () => {
const isOpen = useSelector(state => state.foo.bar.isDialogOpen);
return isOpen ? <div>{/* Dialog content goes here */}</div> : null;
};
In most cases, you don't have to put isOpen
into your global application state. Avoiding prop drilling is not an excuse.
The problem here is not caused by useSelector
itself. However it makes making mistakes easier. On the other hand, if we were using connect
instead, we would think twice to put it into global state.
Maintainability
As Software Developers, our primary job is maintenance of existing code, not writing new ones. As once Martin Fowler said,
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
Before React's introduction of hooks, we used to use presentational components and container components to make sure that business logic is separate from UI code. Hooks have changed that approach. We can now put business logic into custom hooks and use them in multiple components. However, the same is not correct anymore for UI code. Hooks are directly wired into them and it is not possible to use them by connecting to different data sources.
useSelector
is a good example of such cases. I've seen this in many codebases: Components and their sub-components were so tightly coupled into Redux state and therefore developers tended to copy their UI code and create another component instead of using the already available ones. However the solution is simple: Use connect
to create a container component and let the presentational part independently available for future uses. It provides just a right amount of abstraction without much complexity.
Loose coupling
Another problem caused by using useSelector
arises while writing tests for your code. As the hooks are embedded directly into your component it is impossible to test them independent of the application state. Thus, even the simplest components are required to be connected to Redux.
connect
prevents this from happening as well. You can always test your presentational component independently. This allows us to write unit tests specific to that component without connecting it to Redux populated with mock data.
Conclusion
These differences might seem trivial at first glance. But it becomes more and more obvious as project grows. Therefore I suggest to use connect
instead of useSelector
on your projects as well. It will make things harder, yes, but sometimes in software development making something slightly harder is better to prevent immature decisions taken.
Top comments (2)
Good reason to use redux instead hooks. I use redux and redux-saga more than 2 years, but at last one year I turn to simple hooks and redux-saga, without redux.
My decision lead the easy component testing. That means my components works without connect dependency.
Instead of couple useState, I use useReducer which is same action-reducer pattern in react than redux without any boilerplate. If you combine dispatch with action than you use your action with really simple.
Redux-saga without redux: useSagaReducer
Good point. Sometimes I just need to use API response locally. But having redux-saga already in place forces me to use redux store instead. Your solution combines best of two worlds.