Search code examples
javascriptreactjsreduxredux-toolkit

Redux modifying the a state value without being asked to?


i'm using redux to manage my state , my initial state in the reducer contains to arrays ! the first one(games) is the one that i want to modify , the second one (InitialGames) is Initial one that i don't want it to be modified !

the problem is i only make changes on the first array ! but when i console my state after the logic ! i see that both arrays got changed ??? which is confusing !

The case that i'm going into is PLAYER_DEAD

My Reducer

import { ADD_GAME, PLAYER_DEAD, PUT_INFOS, RESET_GAME } from "./actions";

const initialState = {
 games: [],
 InitialGames: [],
};

export default (state = initialState, action) => {
 switch (action.type) {
   case RESET_GAME:
     state.games[action.payload.gameIndex] =
       state.InitialGames[action.payload.gameIndex];
     console.log(state);
     return state;
   case ADD_GAME:
     return {
       games: [...state.games, action.payload.game],
       InitialGames: [...state.games, action.payload.game],
     };
   case PUT_INFOS:
     return {
       gameInfos: action.gameInfos,
     };
   case PLAYER_DEAD:
    
     let newGames = state.games;
     let newInitialGames = state.InitialGames;

     console.log("Before Changings", newGames, newInitialGames);

     let newTeam = newGames[action.payload.indexGame].teams[
       action.payload.index
     ].players.splice(0, 1);


     console.log(
       "After changings",
       newGames,
       newInitialGames
     );
     return {games:newGames,InitialGames:newInitialGames};
 }
 return state;
};

Solution

  • This would be occurring because you're passing the same action.payload.game object to both of your arrays here:

    return {
      games: [...state.games, action.payload.game],
      //               same objects ---------^------v
      InitialGames: [...state.games, action.payload.game],
    }
    

    When you access the .teams array in your PLAYER_DEAD case, you're accessing the same array in memory shared by both games and InitialGames, the same goes for anything within that array, including the .players array within your .teams array's objects. Because you're updating your array in place in a non-immutable way by using .splice(), you end up modifying your state directly and thus modifying the same .players array referenced by both games and InitialGames.

    You need to ensure that you don't modify your state in place by using methods like .splice(). For your particular case, you would do something like so:

    const newGames = state.games.map((game, i) => i === action.payload.indexGame
      ? game.teams.map((team, j) => j === action.payload.index
        ? {...team, players: team.players.slice(1)} // note slice, not splice
        : team
      )
      : game
    );
    

    Above, we map your arrays, updating the items when the inndx matches the item to update. When updating the players array, we use .slice() to remove the first item from the array.

    Writing immutable code isn't always easy, that's why redux toolkit has built-in support for immer when you use an API such as createSlice() API that will allow you to write code like you've been doing the mutates your state. See here for more info.