Today the react-redux team released version 7.1.0, which adds hooks to react-redux! Here’s a quick comparison of how it could change how you write components.
First, A Brief Overview of The New Toys
-
useSelector
: Pass in a function that takes the state as an argument and returns a value. Used to get a single value from state. Can act as a replacement for mapStateToProps. -
useDispatch
: returns a reference to the dispatch object. It can act as a replacement for mapDispatchToProps. -
useStore
: returns an instance of the store. Generally not recommended.
Example With Connect
An example component that stores a query and when a form is submitted to search. I wanted to keep the example simple, so use your imagination for the part where it fetches results.
import React from 'react'
import { connect } from 'react-redux'
const defaultSearchBar = ({ query, updateQuery, handleSearch }) => {
const handleSubmit = (e) => {
e.preventDefault()
handleSearch(query)
}
const handleChange = e => updateQuery(e.target.value)
return (
<form onSubmit={handleSubmit}>
<input
name="search"
value={query}
onChange={handleChange}
/>
</form>
)
}
const mapStateToProps = state => ({
query: state.query,
})
const mapDispatchToProps = dispatch => ({
updateQuery: newQuery => dispatch({ type: 'UPDATE_QUERY', payload: newQuery }),
handleSearch: newSearch => dispatch({ type: 'NEW_SEARCH', payload: newSearch }),
})
const connectedSearchBar = connect(
mapStateToProps,
mapDispatchToProps,
)(defaultSearchBar)
New Example With Hooks
In our new example, we have a few differences: We eliminate the connect function, mapStateToProps, and mapDispatchToProps entirely. This means our component no longer takes in props directly. Now, our form looks like this:
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
const hookedSearchBar = () => {
const dispatch = useDispatch()
const query = useSelector(state => state.query)
const handleSubmit = (e) => {
e.preventDefault()
dispatch({ type: 'NEW_SEARCH', payload: query })
}
const handleChange = e => dispatch({ type: 'UPDATE_QUERY', payload: e.target.value })
return (
<form onSubmit={handleSubmit}>
<input
name="search"
value={query}
onChange={handleChange}
/>
</form>
)
}
Creating Your Own Custom Hooks
If you have code that gets shared frequently between components, you can create a custom hook to keep all of that functionality together. Here’s an example of us isolating the redux-specific part of our form into its own hook:
useSearchQuery = () => {
const dispatch = useDispatch()
const query = useSelector(state => state.query)
const updateQuery = query => dispatch({ type: 'UPDATE_QUERY', payload: query })
const updateSearch = search => dispatch({ type: 'NEW_SEARCH', payload: search })
return { query, updateQuery, updateSearch }
}
Should You Make The Switch?
The ability to create redux hooks above is interesting, but I am also concerned that it could make code harder to test, as testing these components is already dead simple. I’m not sold either way, but I hope this comparison makes it easier for you to make informed decisions about your code base.
What's your take on the new react-redux hooks? I'd love to hear your input in the comments below!
Top comments (18)
To be honest redux hooks sucks, I think they just released for the sake of having hooks, I was afraid of people jumping on hype train and redux apparently did the same.
First when you use redux hooks with your components, it's violating the SRP principle, and your component is bound to redux, now let's say you don't want to use redux anymore, you can't.
If you say you'll just wrap the components using hooks to a separate component and pass the data down as props, then you're just implementing a
connect
, and hooks don't do memoization so performance also suffers.Hooks is one of the best thing which happened to React and I love it, but the same thing is one of the worst things to happen to Redux and I hate it.
The Redux community expressed extremely strong interest in having us add a set of hooks APIs. I suppose you could label this "jumping on the hype train", or you could look at it as us trying to offer solutions that meet the needs of our users.
Hooks are an addition to our API.
connect()
isn't going away. If you want to keep usingconnect()
, go right ahead. Hooks are an alternative. It's your choice which you use in your code.I agree that there's a tighter coupling, but that seems to be the case with hooks in general.
I've noticed this too Mark and I haven't seen a lot written about it. I asked a question on stackoverflow related to this tight-coupling with hooks, but haven't gotten any feedback yet:
stackoverflow.com/questions/564975...
Do you see this as something the community is still evolving best practices around? I liked the ease of testing with presentation components, but I don't see anybody advocating using hooks only in a container component and then rendering a presentation component that takes props and is more loosely-coupled (but more boilerplate). Both Kent C Dodds and Dan Abramov seem to be advising against doing that:
So it seems hooks requires more mocking of any fetch calls in custom hooks and also mandates wrapping components that use redux hooks in a provider, etc. But with the embrace of react testing library philosophy and focusing less on implementation details and more on user experience, maybe this isn't such a bad thing?
Yeah, I think the community is still trying to figure out best practices around hooks in general.
Think about it this way. React came out with mixins at the beginning. It took a couple years for the community and team to say "mixins are bad, use HOCs". It took another couple years for folks to figure out that the "render props" pattern was even possible. Hooks have only been out officially for a few months - we're going to be exploring their implications for a while.
Personally I can only see myself using them in my top level route handler components where I am not concerned about the tight coupling they introduce.
In fairness to Redux, this tight coupling is just a problem that hooks in general introduce.
It's just trade-offs at the end of the day.
This is not true you still can and should have your presentation and control components separate by using a wrapper control component with hooks that passes props down to a presentatioon component that is not aware of redux. It's just that the connect HOC does this without requiring us to write the boiler plate of another control component. So yes, hooks are not a replacement for connect but they are heck a convenience feature in situations where you might need a bit of state and the component is not a re-usable presentation component such as your top level page.
I don't know where you got that from, like I mentioned before, if you're doing on parent level, you're doing what hoc is doing, second it's has performance impact, you can read about it in redux docs. With hoc you can use reselect and connect itself avoids re-renders, but that's not the case with hooks.
At our company after a looooot of consideration, we banned redux hooks from usage.
Except
useSelector
does a strict equality check by default and you can pass a shallow equality comparer function to it for well, shallow comparison just like connect. If any of these techniques result in equality, the hook does not cause a re-render. You do realise that the connect HOC itself is implemented using hooks right? You should read the source code. It's just really easy to use till you figure out hooks.Maybe I am missing something, but I really do not see why one should use Redux in first place.
Why should one prefer Redux over ReactN?
There is something I did not get about it, or it is just that you like to make things over-complicated?
I created an account just to give you a like. I totally agree with you
Nitpick: we have a
useStore()
hook that returns the store instance, rather than auseState()
hook that returns the store state. (If you wanted auseState()
hook, that'd just beconst state = useSelector(state => state)
, but that'd be bad because your component would re-render on every state update.)I agree that the testing question becomes somewhat more difficult - you're basically having to write "integration"-ish tests now to test the component in conjunction with the Redux store state it expects. The React team seems to be encouraging more integration-style testing in general, though.
Thanks for bringing that to my intention. I've fixed the error in the article.
Add that's a good point on testing. You can no longer test just the component by mocking out all of the redux parts, but that may be a feature more than a bug. I've been moving towards more integration-style testing in my work as well. Still, probably one of the bigger differences you'll face moving from connect to hooks.
Thank you, Glenn for the intro to the new Redux hooks.
Would using the new Redux Hooks couple React components with Redux so when one was to re-use the component, the site using the component should also use Redux?
(because when
connect
was used, components could be used by themselves and be connected elsewhere (as it depends only on props)).That's another good point. If you're using these hooks within your component, you would be tightening the coupling that component has with redux. So to your question, it sounds like yes it would.
If you want similar functionality without creating a redux dependency, you could probably create similar functionality with
useContext
anduseState
inside of hooks.Couldn’t you mock out getStoreData in this case?
I am using useSelector to get a reducer data and then trying to store it in a local state using useState hook which appears is not happening synchronously.
The reducer data gets printed in console but the variable set using useState prints empty string instead
I am not sure why this is happening but I had to update the state in the useEffect conditionally instead because of this to store reducer data in the local state using useState hook
I found out about these just yesterday, and I think the main use for them would be for using Redux within a custom hook. This means you can have shared Redux functionality without the need for exhaustive use of
connect()
.However, now that
useReducer()
is built-in to React, I'd be interested to see if it turns out to be a decent replacement for Redux. Especially in cases where shared state is needed in a large section of an app, but not entirely globally.