I'm late to the party; I've been working with React for roughly 4 months and trying to build a project with Typescript and Redux. This past July, it seems like the core React team announced hooks and selector functions was the way to go.
Just so we're all on the same page with what I'm talking about, this LogRocket post describes the container component pattern as:
"...containers are the components connected to the Redux store using the `connect()` method. These containers get some part of global state as props ...and a dispatch method to initiate changes in global state."
I have been watching those Dan Abramov tutorials on lifting state and Egghead's React-Redux over and over.
At my last workplace everything was written in the container component pattern with connect()
and React Redux. In an attempt to prove I can consolidate what I learned and couldn't learn, I'm trying to build a front end oriented expense splitter app (WIP, and any comments are welcome!).
Welcome to Hell when I try to get the value out of an input:
because the property value is buried 2 layers deep, nested in an array of objects.
handleItemChange = (event: any) => {
const { parentNode, value, name } = event.target;
this.setState((prevState: State, props: Props) => {
return {
items: items.map(item => {
if (parentNode.id === item.id) {
if (name === 'name') {
item.name = value;
return {
...item,
name: prevState.inputValue
}
}
if (name === 'cost') {
item.cost = value;
return {
...item,
cost: Number(prevState.inputValue)
}
}
}
}
)
}
})
}
I've heard that the above method is not optimal because:
-
this.setState()
only works on changing the first level of the state object. - inline action methods like
onHandleChange()
are taking up a lot of lines - more experienced devs prefer to use a state immutability lib such as Immer.
In theory, here are some cases for hooks:
- hooks gives you direct access to the state from wherever in the app
- hooks allow state to be used in functional components so that business logic isn't limited to class components.
-
useEffect
will replace lifecycle methods such ascomponentDidMount
andcomponentWillUnmount
, in short, saving lines of code and redundancy in having to write the "cleanup" code...
So, now that I've tried lifting state the hard way, how should I go about refactoring for the better? Where do I start?
Context API with useState()
?
Article No Longer Available
To keep using connect
with useSelector
, as demonstrated in this YouTube tutorial?
Write my own reusable hooks?
Fellow devs, I'd like to know:
- Have you switched over to hooks? Why or why not?
- How do you typically deal with updating a property of the state object that's nested several levels deep?
- Following from the previous question: how do you manage shared state?
- Let's say you are building an app for a pizza shop. Your inventory is updated by one reducer, and the user info is handled by a different reducer. What would you do when a property like updating or cancelling a user's order requires both states? (In Typescript, I would imagine I would create a
type OrderState
that includes both... but isn't that duplicating a lot of data that's already in the App state?)
Recommended Reading
Presentational-Container pattern
Making Sense of React Hooks
Rules of Hooks
Blogged Answers: Thoughts on React Hooks, Redux, and Separation of Concerns
Top comments (3)
Hello Jen, I've recently did some refactoring on one of my apps to using hooks. I'll try to answer some of your questions. Take into consideration I don't use typeScript.
Have you switched over to hooks? Why or why not?
I have switched to hooks because I find the code to be more readable. Also to remove the higher order components and layers over layers which the Redux connect and
withStyles
from material-ui had over the previous version.When I would open the debug for components I would always have the
WithStyles
Connect
(where was the case) components over my components, it was kind of hard to. (I know there is a search I used...)The
useSlector
anduseDispatch
hooks work like a charm without having to use a connect function. For the MaterialUI they haveuseStyles
hook alsoThe life cycles are gone thanks for that also
useEffect
hook dose a pretty good job of you understand how to use it.The only thing that I encountered an issue with is the
shouldComponentUpdate
implementation in hooks, I am still trying to get something to work in that matter...Comparing the hooks and non hooks versions I can say that the hooks is more readable more lightweight and much easier to backtrace and debug.
How do you typically deal with updating a property of the state object that's nested several levels deep?
There are 2 scenarios here from my point:
Component state:
Let's say you have a form with a state which you send a request or something. The form has some inputs and some other components, like an autocomplete which is a component also, with another component in it and so on. If the N level of component controls only the state of the form and nothing else in the app, then I pass the handlers down via props.
For the App state:
Take the example above and say that when we submit the form above we need to update the store. Then we
dispatch
and update action from the form in theonSubmit
.Following from the previous question: how do you manage shared state?
Actually the question for me was how do I manage the data flow between async API calls and multiple actions within the states.
For that I use redux-saga
The one thing I love about redux-saga is that I can control the actions, the outcome of async actions and actually have a precise control over the update of the store and dispatching actions.
To answer your case study. With the pizza shop. (Hope I understood correctly)
Assume that the above form, from the previous question, is a pizza order and on submit you have 2 states to update in the store
User
andInventory
. Update the user orders and remove one item from the inventory.onSubmit
dispatch and actionorderPizza(({ type: 'ORDER_PIZZA_REQUEST' , payload: formState}))
A ReduxSaga would have a
takeLatest
effect on theORDER_PIZZA_REQUEST
which would have a generator function to handle the effectscall
an API to add the order.Assume we don't have an error from the API and we have an
orderId
from the response, we would use theput
effect which actually dispatches an action, to dispatch 2 actions oneINVETORY_REMOVE_ITEM
with thepizzaId
and oneUSER_UPDATE_ORDERS
whit theorderId
In the
userReducer
handle theUSER_UPDATE_ORDERS
and in theinvetoryReducer
handle theINVENTORY_REMOVE_ITEM
Hi Sabin, thank you so much for your thorough response; I thought that my long rambling post totally lost and bored people. And yes you pulled the question out of my mouth:
" how do I manage the data flow between async API calls and multiple actions within the states?"
From component to app state, yes. I realize after re-reading your answer a few times, that my initial confusion was due to an assumption about architecture. I had not thought about async actions and was conflating data types with component state, thinking each data type required a state and a corresponding reducer, (i.e.
UserState
,ItemState
for each pizza,InventoryState
,OrderState
), which is quite superfluous. That dogpiled into an assumption that in order for an action to update multiple states, (instead of async actions like you suggested), that reducers would receive multiple state types as params (Just imagine the state object being returned with mismatching and redundant info!)I also realize I had not looked into how hooks are referred to in reducers; that should close the loop :D
This is all to say, thank you for walking me through the example with such clarity.
I've started using React Hooks and find that they let you write better and cleaner code. The downside is that they are very different from the "traditional" approach in React and it takes some time to get your head around them. This can be an issue in a team where many devs are used to the "traditional" way of developing React components and don't really have the time or energy to re-learn everything they know about React.