I'm looking at this Redux tutorial where the following reducer is being discussed:
function visibilityFilter(state = 'SHOW_ALL', action) {
return action.type === 'SET_VISIBILITY_FILTER' ?
action.filter :
state
}
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([{
text: action.text, completed: false
}]);
case 'TOGGLE_TODO':
return state.map((todo, index) =>
action.index === index ?
{ text: todo.text, completed: !todo.completed } :
todo
)
default: return state;
}
}
function todoApp(state = {}, action) {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
};
}
What it does it clear, however I don't get why it does the state.concat
/ state.map
to duplicate the state instead of working on it directly. I understand it's to achieve immutability, but, technically, what could go wrong if I change the code from this:
return state.map((todo, index) =>
action.index === index ?
{ text: todo.text, completed: !todo.completed } :
todo
)
to this:
state[action.index].completed = !state[action.index].completed;
return state;
The state that was passed to the reducer is obsolete anyway, so whether it has been changed or not it must not be used anywhere (and if I'm not mistaken that's indeed what Redux is doing - it ignores the previous state and takes the new one as the "source of truth"). So only the new state returned by the function should matter.
So if I follow this approach of modifying the state directly in the reducer and returning this, what bug could that create in my application? Any idea?
Redux restricts updating the state to this method only. This strict way of updating the state ensures that the state can not be changed directly either by view or any network callback. The only way to update a state is by defining the action and then dispatching it. Remember that actions are plain JavaScript objects.
Mutating state is an anti-pattern in React. React uses a rendering engine which depends on the fact that state changes are observable. This observation is made by comparing previous state with next state. It will alter a virtual dom with the differences and write changed elements back to the dom.
The fact that the Redux state changes predictably opens up a lot of debugging possibilities. For example, using time travel makes it possible to travel back and forth between different states instead of having to reload the whole app in order to get back to the same place.
The state is updated and managed by reducers. Reducers always have to return something even if it's null ; they should never return undefined . If a reducer's state is an object, you must always return a new object instead of editing the object in place.
Redux compares your old state to your new state with === to know if it changed. If you mutate the state instead of creating a new copy, this test will fail and your components won't update.
The core of Redux does not care about immutability. It also does not actually do anything to prevent mutations, either inside reducers or in other partso f your application. However, mutations will break time-travel debugging, as well as the React-Redux connect
function. There's a new section of the Redux FAQ on Immutable Data that describes in more detail how and why Redux relies on immutability, and the question on Why isn't my component re-rendering? also applies.
Also, I'm currently working on a blog post that will discuss what technical limitations Redux actually requires, vs how you are intended to use Redux, vs how it's possible to use Redux. I'm hoping to have that post up within the next week. If you're interested, keep an eye on my blog at http://blog.isquaredsoftware.com.
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