Home > Enterprise >  React's strict mode double render shows inconsistent state
React's strict mode double render shows inconsistent state

Time:12-31

I'm aware React renders twice in Strict mode in development, this is to ensure the function is pure and there are no side effects. However in my case it seems to not be consistent while getting the state. It seems that when replaying the function, it has 1 updated state parameter and 1 non-updated. I understand this might not make sense, so here's some code with debugging information. Its a basic tic tac toe game

const tttReducer = (state, action) => {
  // generate a random number to mark whether the replay is running or original
  const running = Math.random()*100;

  // print initial state
  console.log(running, 'initial', 'nextplayer', state.nextPlayer, state.board[0], 
  state.board[1], state.board[2]);

  // If state on the board has been set, return the state as it is
  if (state.board[action.payload.y][action.payload.x] !== null) {
    console.log(running, 'if', 'nextplayer', state.nextPlayer, state.board[0], state.board[1], state.board[2]);
    return state
  }

  // Mark on the board, change the player and return
  let board = [...state.board]
  board[action.payload.y][action.payload.x] = state.nextPlayer
  const nextPlayer = state.nextPlayer === 'X' ? 'O' : 'X'
  console.log(running, 'else', 'nextplayer', nextPlayer, board[0], board[1], board[2]);
  return {
    ...state,
    board,
    nextPlayer,
  };
}

const [state, dispatch] = useReducer(tttReducer, {
    board: [[null, null, null],
    [null, null, null],
    [null, null, null]],
    nextPlayer: 'X',
    winner: null,
})
19.995504281947607 'initial' 'nextplayer' 'X' (3) [null, null, null] (3) [null, null, null] (3) [null, null, null]
App.jsx:32 19.995504281947607 'else' 'nextplayer' 'O' (3) ['X', null, null] (3) [null, null, null] (3) [null, null, null]
react_devtools_backend.js:4066 7.972281733648767 initial nextplayer X (3) ['X', null, null] (3) [null, null, null] (3) [null, null, null]
react_devtools_backend.js:4066 7.972281733648767 if nextplayer X (3) ['X', null, null] (3) [null, null, null] (3) [null, null, null]

The first 2 lines of log show the initial state, and then the "else" path is taken, i.e. the box on the board is marked. So far so good.
The next 2 lines show the replay in Strict mode in development. This SHOULD work fine because we're not mutating state directly. However as you can see in the log, the board it is printing is the updated board, i.e. X is marked in the first row and col, BUT nextPlayer is NOT updated to be 'O'. Why?

If the replay uses updated state, the nextPlayer should be 'O' initially.
If the replay does not use the updated state, then the board should still be the initial board.

I'm a little new to React, and I've spent a lot of time debugging this. I'd love help or explanation. Please let me know if something is not clear here.

CodePudding user response:

let board = [...state.board] create a shallow copy of the board

board is a new array, but the nested arrays are the same so

board[action.payload.y][action.payload.x] = state.nextPlayer

edits the old array


To create a deep copy use:

let board = state.board.map(row => [...row])
  • Related