handris
handris

Reputation: 2109

What problem is caused by only updating the top level of nested data in redux?

I have part of a reducer as an example:

case TOGGLE_FAVORITE: {
  const cropId = Number(action.cropId);
  const crop = state.crops.find(cr => cr.id === cropId);
  crop.isFavorite = !crop.isFavorite;
  return {
    ...state
  };
}

I am updating only the top level state. This is wrong according to the redux documentation, but it doesn't explain why exactly. 'isFavorite' is a toggleable field, and the change is reflected in a react component, so the change of the 'isFavorite' field is visibly happening.

I was under the impression, that updating only the top level state must be avoided in redux, because the 'state.crop' reference won't change in the above case, and React will not know it should rerender.

My question is, why exactly is the above code example wrong? What problems could it cause down the line? Why does it seem to be working?

Upvotes: 1

Views: 37

Answers (1)

Dennis Vash
Dennis Vash

Reputation: 53934

You can find plenty pro claims for using immutable data by reading the Immutable Data Management reading list that you find in docs.

But I believe that foremost example will be when you subscribed to a partial state change:

const cropId = CROP_ID_TO_FIND;
// Won't render component!
const myCropObj = useReducer(state => state.crops.find(cr => cr.id === cropId));

Here is a full example to check my claim, notice that pressing "Mutable" button won't update state, where "Immutable" does:

// reducers
const list = (state = [], action) => {
  switch (action.type) {
    case "MUTATE_STATE": {
      const [initial] = state;
      initial.text = "mutate state";
      return [...state];
    }
    case "IMMUTABLE_STATE": {
      return [{ text: "immutable state" }];
    }
    default:
      return state;
  }
};

const store = createStore(combineReducers({ list }), {
  list: [{ text: "initial" }]
});

function MutateState() {
  const dispatch = useDispatch();
  return (
    <button onClick={() => dispatch({ type: "MUTATE_STATE" })}>Mutable</button>
  );
}

function ImmutableState() {
  const dispatch = useDispatch();
  return (
    <button onClick={() => dispatch({ type: "IMMUTABLE_STATE" })}>
      Immutable
    </button>
  );
}

function SelectorState() {
  // Subscribed to a partial state
  const list = useSelector((state) => state.list[0]);
  console.log("rendered on subscribe change");
  return (
    <>
      <h1>Store</h1>
      <pre>{JSON.stringify(list, null, 2)}</pre>
    </>
  );
}

ReactDOM.render(
  <Provider store={store}>
    <MutateState />
    <ImmutableState />
    <SelectorState />
  </Provider>,
  document.getElementById("root")
);

Edit Redux-Example

Upvotes: 1

Related Questions