What could happen if modifying state directly inside a Redux reducer?
Asked Answered
O

3

8

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?

Omentum answered 3/5, 2017 at 11:42 Comment(1)
This answer explains in details https://mcmap.net/q/1323728/-how-is-state-immutability-actually-used-in-reduxJudas
N
14

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.

Nilotic answered 3/5, 2017 at 15:19 Comment(4)
Thank you, I think that's the key, the way Redux compares states before and after sending them through the reducers. If it was making a deep comparison, we wouldn't need to care about this. I feel it's a bit of a leaky abstraction actually, but one that's necessary for performance reasons.Omentum
I don't think it's a leaky abstraction. Immutable programming is a well known paradigm, and gaining more traction in the JS community.Nilotic
I'm not entirely sure about this. Nobody here (and elsewhere, and in anything I've read) could explain what bug would be introduced if the state is changed, then returned from within the reducer. Ok the reason is the === that Redux is doing, which is an optimization. But if it was doing a deep comparison, there would be no point to immutability. That being said, maybe I'm missing something.Omentum
I would also say that updating the state directly would make it difficult to replay the app in reverse as well as seeing clearly what was the state before a given action was dispatched. Are my assumptions right too?Nexus
C
4

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.

Caresse answered 3/5, 2017 at 15:34 Comment(0)
I
0

Working with redux and the action-reducers pattern is all about pure functions. Reducers should be pure functions, which means that they should not mutate the state. Pure functions take an input and return a new output. In case redux didn't use this pattern and the state was mutated then the data would be unreliable and as result cause bugs in your application for example your react components would not get updated.

Incendiary answered 3/5, 2017 at 15:14 Comment(1)
If we know what we are doing. It should not be a problem. An Example is when we want to use redux just as a global storage. Imaging a tree, that can grow to any size. Copying it each time fully can be a big performance hit, we can just mutate it. And we never directly use it to update our component. And we trigger the re-rendering at update, by forcing the re-rendering. Such functionalities are provided for such extreme cases. And comment would help to clarify things.Dimerous

© 2022 - 2024 — McMap. All rights reserved.