In the following code snippet, I have a working reducer that I believe is incorrect in that is re-using existing state and not replacing it. It does work though with no error which concerns me.
The state that is coming into this reducer is just a simple array with just first level objects. That is, something like:
const stateIn = [{id: 101,favorite: 0},{id: 102,favorite: 1},...]
And the reducer I'm suspicious of is below:
const speakersReducer = (state, action) => {
switch (action.type) {
case "loadspeakers": {
return action.data;
}
case "favorite": {
return state.map((item, index) => {
if (item.id === action.sessionId) {
// let speakerToUpdate = Object.assign(item);
// speakerToUpdate.favorite = 1;
item.favorite = 1;
return item;
// return speakerToUpdate;
}
return item;
});
}
...
default:
return state;
}
};
export default speakersReducer;
Is this correct?
The mutation of item is inside the "favorite" case is, indeed, suspicious, as @Tholle pointed out. Mutation in reducers is generally considered an anti-pattern and can frequently lead to bugs. I would definitely recommend changing it to a immutable pattern.
But then...
Why doesn't this cause an error, assuming it's not correct?
Mutability in reducers is a common source of errors, but whether a particular bit of mutation causes an error depends on how its mutated and how the reducer is used.
While the favorites case may incorrectly reuse item objects from one version of the state to another, it always produces a new state array, since Array.prototype.map always returns a new array. So, for example, the useReducer which only cares about the identity of the outer state object will correctly trigger a re-render, despite the mutation.
On the other hand, a redux selector memoized on a specific item in the array would incorrectly return old values. And if old items are cached and reused, the mutation could cause unexpected bugs there as well. But those are fairly specific cases, so in most cases, I suspect this would work. (I would still change it to not mutate, though)
Any tools that would help me verify correctness for state updates?
What you want is some form of immutability: there's a number of libraries for immutability in JS.
Also Typescript can be used either alone, or in combination with these libraries: allowing objects and arrays to be declared as readonly, and raising compiler errors if you mutate them.
A linter rule might help, too, but it would be tricky unless you're okay with forbidding all mutation in the entire codebase, or else manually toggling the rule on inside reducers.
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