When creating a React app, if I use the hook useSelector
, I need to adhere to the hooks invoking rules (Only call it from the top level of a functional component). If I use the mapStateToProps
, I get the state in the props and I can use it anywhere without any issues... Same issue for useDispatch
What are the benefits of using the hook besides saving lines of code compared to mapStateToProps
?
The useDispatch hook is used to dispatch an action while useSelector hook is used to get the state from the redux store.
As the first argument passed in to connect , mapStateToProps is used for selecting the part of the data from the store that the connected component needs. It's frequently referred to as just mapState for short. It is called every time the store state changes.
The mapStateToProps and mapDispatchToProps deals with your Redux store's state and dispatch , respectively. state and dispatch will be supplied to your mapStateToProps or mapDispatchToProps functions as the first argument.
useSelector() Allows you to extract data from the Redux store state, using a selector function.
Redux store state can be read and changed from anywhere in the component, including callbacks. Whenever the store state is changed the component rerenders. When the component rerenders, useSelector runs again, and gives you the updated data, later to be used wherever you want. Here is an example of that and a usage of useDispatch
inside a callback (after an assignment in the root level):
function Modal({ children }) { const isOpen = useSelector(state => state.isOpen); const dispatch = useDispatch(); function handleModalToggeled() { // using updated data from store state in a callback if(isOpen) { // writing to state, leading to a rerender dispatch({type: "CLOSE_MODAL"}); return; } // writing to state, leading to a rerender dispatch({type: "OPEN_MODAL"}); } // using updated data from store state in render return (isOpen ? ( <div> {children} <button onClick={handleModalToggeled}>close modal</button> </div> ) : ( <button onClick={handleModalToggeled}>open modal</button> ); ); }
There is nothing you can do with mapStateToProps/mapDispatchToProps that you can't do with the useSelector and useDispatch hooks as well.
With that said, there are a couple of differences between the two methods that are worth considering:
mapStateToProps
, container logic (the way store data is injected into the component) is separate from the view logic (component rendering). useSelector
represents a new and different way of thinking about connected components, arguing that the decoupling is more important between components and that components are self contained. Which is better? Verdict: no clear winner. source connect
function usually means there should be another additional container component for each connected component, where using the useSelector
and useDispatch hooks is quite straightforward. Verdict: hooks have better DX.useSelector
can run before the newest updated props come in. These are mostly rare and avoidable edge cases, but they had been already worked out in the older connect
version. verdict: connect is slightly more stable than hooks. source connect
has some advanced techniques, using merge props and other options hidden in the connect function. useSelector
accepts a second argument - an equality function to determine if the state has changed. verdict: both are great for performance in advanced situations.connect
is a nightmare. I remember myself feverishly writing three props interfaces for each connected component (OwnProps, StateProps, DispatchProps). Redux hooks support types in a rather straightforward way. verdict: types are significantly easier to work with using hooks.TL;DR - Final verdict: each method has its merits. connect
is more mature, has less potential for weird bugs and edge cases, and has better separation of concerns. Hooks are easier to read and write, as they are collocated near the place where they are used (all in one self contained component). Also, they are easier to use with TypeScript. Finally, they will easily be upgradable for future react versions.
I think you misunderstand what "top level" is. It merely means that, inside a functional component, useSelector()
cannot be placed inside loops, conditions and nested functions. It doesn't have anything to do with root component or components structure
// bad const MyComponent = () => { if (condition) { // can't do this const data = useSelector(mySelector); console.log(data); } return null; } --- // good const MyComponent = () => { const data = useSelector(mySelector); if (condition) { console.log(data); // using data in condition } return null; }
If anything, mapStateToPtops
is located at even higher level than a hook call
the rules of hooks make it very hard to use that specific hook. You still need to somehow access a changing value from the state inside callbacks
To be fair you almost never have to access changing value inside a callback. I can't remember last time I needed that. Usually if your callback needs the latest state, you are better off just dispatching an action and then handler for that action (redux-thunk, redux-saga, redux-observable etc) will itself access the latest state
This is just specifics of hooks in general (not just useSelector) and there are tons of ways to go around it if you really want to, for example
const MyComponent = () => { const data = useSelector(mySelector); const latestData = useRef() latestData.current = data return ( <button onClick={() => { setTimeout(() => { console.log(latestData.current) // always refers to latest data }, 5000) }} /> ) }
What are the benefits of using the hook besides saving lines of code compared to mapStateToProps?
mapStateToProps
and you will have to scroll all the way to mapStateToProps
in the file to find out which selector is used for this specific prop. This is not the case with hooks where selectors and variables with data they return are coupled on the same linemapStateToProps
you usually have to deal with mapDispatchToProps
which is even more cumbersome and easier to get lost in, especially reading someone else's code (object form? function form? bindActionCreators?). Prop coming from mapDispatchToProps
can have same name as it's action creator but different signature because it was overridden in mapDispatchToprops
. If you use one action creator in a number of components and then rename that action creator, these components will keep using old name coming from props. Object form easily breaks if you have a dependency cycle and also you have to deal with shadowing variable names.
import { getUsers } from 'actions/user' class MyComponent extends Component { render() { // shadowed variable getUsers, now you either rename it // or call it like this.props.getUsers // or change import to asterisk, and neither option is good const { getUsers } = this.props // ... } } const mapDispatchToProps = { getUsers, } export default connect(null, mapDispatchToProps)(MyComponent)
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