Trying to change the text depending on whether the game is over or not react.js

I’ve made a tic-tac-toe game (react’s own documentation), now I’m trying to change some features, in this case the first thing I changed is that user can observe the previous moves once the game is over. I’ve came up with the result that I was looking for, but I have to click one more time to make the winner value to change and therefore, to make the bottom buttons appear(the ones who let you flick through the game history move by move). Do you know what causes that? How can I fix it?

Here’s my Code: (I’ve tried several solutions but none did work, I have no idea on what causes this issue and that’s why I’m putting the whole code)

function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6],
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;

class Board extends React.Component {
  renderSquare(i) {
    return <Square value={this.props.squares[i]} 
    onClick={()=>{this.props.onClick(i)}}/>;
  }
  
  render() {
    return (
      <div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
            {this.renderSquare(7)}
            {this.renderSquare(8)}
          </div>
        </div>
    );
  }
}
  
class Game extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      history: [{
        squares: Array(9).fill(null)
      }],
      stepNumber: 0,
      xIsNext: true,
      winner: null
    }
  };

  handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    if (calculateWinner(squares) || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? 'X' : 'O';
    this.setState({
      history: history.concat([{
        squares: squares,
      }]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext,
    });
  }

  jumpTo(step) {
    this.setState({
      stepNumber: step,
      xIsNext: (step % 2) === 0,
    });
  }
  
  checkForWinner(squares,winr) {
    if (winr === null)
      this.setState({
        winner: calculateWinner(squares)
      })
  }
  
  render() {
    const history = this.state.history;
    const current = history[this.state.stepNumber];
    const winner = this.state.winner

    const moves = history.map((_, move) => {
      const desc = move ?
        'Go to move #' + move :
        'Go to game start';
      return (
        <li key={move}>
          <button onClick={() => this.jumpTo(move)}>{desc}</button>
        </li>
      );
    });

    return (
      <div className="game">
        <div className="game-board">
          <Board
            squares={current.squares}
            onClick={i => {
              if (!winner) {
                this.handleClick(i)
              }
              this.checkForWinner(current.squares, winner)
            }}
            
          />
        </div>
        <div className="game-info">
          <div>{(winner) ? 'Winner: ' + winner : 'Next player: ' + (this.state.xIsNext ? 'X' : 'O')}</div>
          <ol>{(winner) ? moves : null}</ol>
        </div>
      </div>
    );
  }
}
 
ReactDOM.render(
  <Game />,
  document.getElementById('root')
);

Answer

When you check for a winner while it should be a winner, the squares still not updated yet (this.handleClick does change the state but the change itself is asynchronous).

I’d recommend to move the winner check into the click handler

handleClick(i) {
  const history = this.state.history.slice(0, this.state.stepNumber + 1);
  const current = history[history.length - 1];
  const squares = current.squares.slice();
  if (squares[i]) {
    return;
  }
  squares[i] = this.state.xIsNext ? "X" : "O";
  this.setState({
    history: history.concat([
      {
        squares: squares
      }
    ]),
    stepNumber: history.length,
    xIsNext: !this.state.xIsNext,
    winner: calculateWinner(squares) // <-- this
  });
}

https://codesandbox.io/s/epic-sutherland-gj6d5?file=/src/App.js:1996-2028