react-redux: update specific position in nested state

Viewed 86

So for a TictacToe game I would have a nested array in the state, like this:

field: [
    [-1, -1, -1],
    [-1, -1, -1],
    [-1, -1, -1],
],

Now I want to set a specific cell to X or O, but I couldn't get it to completely rerender. I have 2 function components and whatever I tried I could never get the cells to be updatet. The reducer correctly updates my state though. So should I write the reducer or change anything, so that each cell is rerendered if it is changed?

Code samples. Obviously there is more, but I believe this should be enough.

function Ttt_game(props){
return (
        <div>
            <table>
                <tbody>
                    {
                        props.field.map((row, i) => (<Ttt_row row={row} key={i} row_index={i} />))
                    }
                </tbody>
            </table>
        </div>
    )
}

and

function Ttt_row(props){
return (
        <tr>
            {
                row.map((cell, i) => (
                    <td key={i}>
                        <div onClick={() => insertIntoField({ x: row_index, y: i, symbol })}>
                            {cell}
                        </div>
                    </td>
                ))
            }
        </tr>
    )
}

reducer:

case "ttt_insert":
        let newField = state.field;
        newField[action.payload.x][action.payload.y] = action.payload.symbol;
        return {
            ...state,
            field: newField
        };
2 Answers

You look to be mutating your field state object. If you aren't using redux-toolkit or Immer then this is a no-no.

case "ttt_insert":
  let newField = state.field; // <-- newField is reference to state object
  newField[action.payload.x][action.payload.y] = action.payload.symbol;
  return {
    ...state,
    field: newField
  };

You should shallow copy your field state object then update it. You need to shallow copy any nested objects/arrays that are being updated

case "ttt_insert":
  const { payload } = action;
  
  return {
    ...state,
    field: state.field.map((row, x) =>
      x === payload.x
        ? row.map((col, y) => (y === payload.y ? payload.symbol : col))
        : row
    )
  };

you are mutating the field object hence it is not updating. Object and arrays are mutable, so you must create new array; the one which is updated.

below is the example using .map function.

your reducer will be like this:

const { field } = state;
const newField = field.map((row, rIndex) => {
  if (rIndex !== action.payload.x) {
    return row;
  }
  return row.map((col, cIndex) => {
    if (cIndex !== action.payload.y) {
      return col;
    }
    return action.payload.symbol;
  })
})
return {
  ...state,
  field: newField,
}

Related