Are you looking for a way to update state that's an array or an object that has a nested structure? I have good news. immer makes update nested state incredibly easy.
Let's look at an example first.
The Old Way
In my project imbiased, for the topics I have a state structure like this:
{
"topicResourceList": [
{
"id": 25,
"userId": 0,
"userName": null,
"question": "Coffee or Smoothie",
"left": {
"id": 49,
"name": "Coffee",
"count": 1
},
"right": {
"id": 50,
"name": "Smoothie",
"count": 0
},
"commentCount": 4,
"createdAt": "2020-05-26T01:36:23.0157452",
"editedAt": "2020-05-26T01:36:23.0157453"
},
{
"id": 24,
"userId": 0,
"userName": null,
"question": "Dog or Cat",
"left": {
"id": 47,
"name": "Dog",
"count": 1
},
"right": {
"id": 48,
"name": "Cat",
"count": 0
},
"commentCount": 1,
"createdAt": "2020-05-26T01:35:04.0794192",
"editedAt": "2020-05-26T01:35:04.0794192"
},
]
}
Let's say someone created a comment on the first topic. Now I need to update the commentCount
in the corresponding topic. The old way of doing it is we have to create a copy of the state and only change the comment count. One way to do it is using map
:
return {...baseState, baseState.topicResourceList.map((topic, idx) => {
if (idx === 0) {
return {
...topic,
commentCount: topic.commentCount + 1
}
}
return topic
})}
This doesn't seem that bad. What if someone voted? Let's say someone voted for the left side on the first topic.
return {...baseState, baseState.topicResourceList.map((topic, idx) => {
if (idx === 0) {
return {
...topic,
left: {
...topic.left,
count: topic.left.count + 1
}
}
}
return topic
})}
Things are starting to get a little bit messy. If the state is 3, 4 levels deep, updating the state would be a nightmare. Luckily, there's an incredibly easy way to do this: immer.
Using immer
immer makes this tremendously easier, To update commentCount
all I have to do in my reducer is:
import produce from "immer"
return produce(baseState, draft => {
draft.topicResourceList[0].commentCount++
})
To update the vote count:
return produce(baseState, draft => {
draft.topicResourceList[0].left.count++
})
Isn't that amazing? Along with immer, I discovered redux-toolkit, which gives an opinionated way of using redux and has immer built in.
Normally we put reducers and action creators into separate files, redux toolkit introduces something called the slice, which combines reducers and action creators into one single file. I think using redux-toolkit will make my code cleaner, so in my following projects I may start using redux-toolkit. I definitely recommend checking it out.