Let's say we have the old traditional way of React / Redux: (you don't need to expand the code if you are familiar with it:)
import React from 'react';
import { connect } from 'react-redux';
function Count(props) {
return (
<div>
<button onClick={props.increment}> + </button>
{props.count}
<button onClick={props.decrement}> - </button>
</div>
);
}
const mapStateToProps = state => ({
count: state.count
});
const mapDispatchToProps = dispatch => ({
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' })
});
export default connect(mapStateToProps, mapDispatchToProps)(Count);
Now, using React Hooks useSelector()
and useDispatch()
, the above code could be written as this way:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
function Count() {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
const increment = () => dispatch({ type: 'INCREMENT' });
const decrement = () => dispatch({ type: 'DECREMENT' });
return (
<div>
<button onClick={increment}> + </button>
{count}
<button onClick={decrement}> - </button>
</div>
);
}
export default Count;
Both versions work exactly the same, by themselves, except, isn't version 1 highly reusable for Count
? That's because using a different mapStateToProps()
and mapDispatchToProps()
, we can use connect()
again to create another CountNoodle()
and now we have reused Count()
.
For version 2, Count()
is hard-wired with what state and dispatch it uses, so the whole Count()
is entirely not-reusable. That is, it has to be used with that particular state and particular dispatch, but nothing else. Isn't it true? So is version 2 above not recommended and actually you would have a version 3, which is not to call it Count()
but called it CountNoodle()
and "wire up" the state and dispatch, and re-use Count()
, which would be simply "presentational"?
So it may look something like this:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
// Count() actually would be in a different file and CountNoodle.js
// would import that file
function Count({count, increment, decrement}) {
return (
<div>
<button onClick={increment}> + </button>
{count}
<button onClick={decrement}> - </button>
</div>
);
}
function CountNoodle() {
const count = useSelector(state => state.countNoodle);
const dispatch = useDispatch();
const increment = () => dispatch({ type: 'INCREMENT_NOODLE' });
const decrement = () => dispatch({ type: 'DECREMENT_NOODLE' });
return <Count ...{count, increment, decrement} />;
// or return Count({count, increment, decrement});
}
export default CountNoodle;
Redux vs. On the other hand, with React Hooks and the useContext API, there is no need to install external libraries or add a bunch of files and folders to make our app work. This makes it a much simpler, more straightforward approach to handling global state management in React applications.
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. These hooks were first added in v7.1.0.
Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That's what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.
The short answer is no, Redux is not necessary for React applications. However, there are always clear warning signs when you should be using a state management library like Redux and signs that you shouldn't.
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.
React also lets us write custom hooks, which let us extract reusable hooks to add our own behavior on top of React's built-in hooks. React Redux includes its own custom hook APIs, which allow your React components to subscribe to the Redux store and dispatch actions.
There are some architectural trade offs to take into consideration when deciding whether to use hooks or not. Mark Erikson summarizes these nicely in his two blog posts Thoughts on React Hooks, Redux, and Separation of Concerns and Hooks, HOCs, and Tradeoffs.
React Redux now includes its own useSelector and useDispatch Hooks that can be used instead of connect. useSelector is analogous to connect ’s mapStateToProps.
I addressed this question in my post Thoughts on React Hooks, Redux, and Separation of Concerns and my ReactBoston 2019 talk on "Hooks, HOCs, and Tradeoffs".
I'd encourage you to read/watch through both of them, but as a summary:
Hoc version (version 1) of your Count
component is also "hard-wired" with particular state and dispatch. You are doing it when export default connect(mapStateToProps, mapDispatchToProps)(Count);
To compare both hoc and hook approaches to achieve Count
reuse lets take a look at your original Count
:
function Count(props) {
return (
<div>
<button onClick={props.increment}> + </button>
{props.count}
<button onClick={props.decrement}> - </button>
</div>
);
}
And to reuse it with hocs you'll go like:
const withCount = (field, Component) => connect(
state => ({ count: state[field] }),
dispatch => ({
increment: () => dispatch({ type: 'INCREMENT', payload: field }),
decrement: () => dispatch({ type: 'DECREMENT', payload: field })
})
)(Component)
const NoodleCount = withCount("noodle", Count)
const DrinksCount = withCount("drinks", Count)
And for the hooks you could:
function useCount(field) {
const count = useSelector(state => state[field]);
const dispatch = useDispatch();
const increment = () => dispatch({ type: 'INCREMENT', payload: field });
const decrement = () => dispatch({ type: 'DECREMENT', payload: field });
return {count, increment, decrement}
}
const NoodleCount = () => <Count {...useCount("noodle")} />;
const DrinksCount = () => <Count {...useCount("drinks")} />;
Obviously, there are pros and cons for both of approaches but:
Does using React Hooks drastically reduce how code can be reused in React / Redux?
No, hooks can not be the source of troubles in component reusability.
Hope it makes sense
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With