Code a Tic-Tac-Toe game in React, part 2

Here's another way to code a Tic-Tac-Toe game in React

By: Ajdin Imsirovic 22 March 2023

In part 1 I’ve shown you one way to code a game in React. In this part, I’ll show you an alternative way to achieve the same goal.

Step 1: Create the Board component

Create a new file named Board.js in the src folder, and add the following code:

import React from 'react';

function Board() {
  return (
    <div>
      <h1>Tic-Tac-Toe</h1>
      <div className="board">
        {/* The board squares will go here */}
      </div>
    </div>
  );
}

export default Board;

This creates a new component called Board, which will display the game board.

Step 2: Add the Square Component

Create a new file called Square.js in the src folder, and add the following code:

import React from 'react';

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

export default Square;

This creates a new component called Square, which will represent each square on the game board.

Step 3: Add state to the Board component

In order to keep track of the game state, we need to add some state to the Board component. Add the following code to the Board component:

import React, { useState } from 'react';
import Square from './Square';

function Board() {
  const [squares, setSquares] = useState(Array(9).fill(null));
  const [xIsNext, setXIsNext] = useState(true);

  function handleClick(i) {
    const newSquares = squares.slice();
    newSquares[i] = xIsNext ? 'X' : 'O';
    setSquares(newSquares);
    setXIsNext(!xIsNext);
  }

  function renderSquare(i) {
    return (
      <Square
        value={squares[i]}
        onClick={() => handleClick(i)}
      />
    );
  }

  const status = `Next player: ${xIsNext ? 'X' : 'O'}`;

  return (
    <div>
      <h1>Tic-Tac-Toe</h1>
      <div className="status">{status}</div>
      <div className="board">
        <div className="board-row">
          {renderSquare(0)}
          {renderSquare(1)}
          {renderSquare(2)}
        </div>
        <div className="board-row">
          {renderSquare(3)}
          {renderSquare(4)}
          {renderSquare(5)}
        </div>
        <div className="board-row">
          {renderSquare(6)}
          {renderSquare(7)}
          {renderSquare(8)}
        </div>
      </div>
    </div>
  );
}

export default Board;

This code adds two pieces of state to the Board component: “squares” and “xIsNext”. “squares” is an array of 9 values, representing the state of each square on the game board. “xIsNext” is a boolean value that indicates whose turn it is (true for X, false for O).

The handleClick function is called when a square is clicked. It updates the “squares” state with the new value and toggles the “xIsNext” state.

The renderSquare function takes an index i and returns a Square component with the corresponding value and onClick function.

Finally, the status variable displays whose turn it is.

Step 4: Add CSS Styling

To make the game look nice, we’ll add some CSS styling. Add the following code to the index.css file in the src folder:

body {
  font-family: sans-serif;
  text-align: center;
}

h1 {
  margin-top: 50px;
}

.board {
  display: inline-block;
  margin-top: 20px;
}

.square {
  background-color: #fff;
  border: 1px solid #999;
  float: left;
  font-size: 24px;
  font-weight: bold;
  line-height: 34px;
  height: 34px;
  margin-right: -1px;
  margin-top: -1px;
  padding: 0;
  text-align: center;
  width: 34px;
}

.square:focus {
  outline: none;
}

.board-row:after {
  clear: both;
  content: "";
  display: table;
}

.status {
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 10px;
}

This CSS adds some basic styling to the game board and squares.

Step 5: Render the Board component

Finally, we need to render the Board component in the index.js file. Replace the existing code with the following:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Board from './Board';

ReactDOM.render(
  <React.StrictMode>
    <Board />
  </React.StrictMode>,
  document.getElementById('root')
);

This code imports the Board component and renders it in the root element of the HTML document.

Step 6: Reseting the game after completion

To reset the game after one game is played, we need to add a reset button to the user interface and handle the click event of that button in our Board component.

Add the following button element to the render method of the Board component:

<button onClick={() => resetGame()}>Reset</button>

This button will call the resetGame method when clicked.

Next, we’ll implement the resetGame method.

Add the following code to the Board component to implement the resetGame method:

const resetGame = () => {
  setSquares(Array(9).fill(null));
  setXIsNext(true);
};

This code resets the squares array to an initial state where all values are null and sets xIsNext to true, indicating that X will go first.

To wrap it up, add the styles for the reset button:

button {
  margin: 10px;
  font-size: 20px;
  font-weight: bold;
  padding: 10px 20px;
  background-color: #fff;
  color: #333;
  border: 2px solid #333;
  border-radius: 5px;
  cursor: pointer;
}

button:hover {
  background-color: #333;
  color: #fff;
}

Step 7: Adding the winning conditions

Here are the steps:

  • 7.1 Define the winning combinations
  • 7.2 Check for a winner
  • 7.3 Update the handleClick function
  • 7.4 Update the status variable

Let’s go through each of the steps.

7.1 Define the winning combinations

Define an array of arrays that represents all possible winning combinations on the Tic-Tac-Toe board. Add the following code at the top of the Board component:

const WINNING_COMBINATIONS = [
  [0, 1, 2],
  [3, 4, 5],
  [6, 7, 8],
  [0, 3, 6],
  [1, 4, 7],
  [2, 5, 8],
  [0, 4, 8],
  [2, 4, 6],
];

This array represents all possible ways to win the game.

7.2 Check for a winner

Add the following function to the Board component to check if there is a winner:

const calculateWinner = (squares) => {
  for (let i = 0; i < WINNING_COMBINATIONS.length; i++) {
    const [a, b, c] = WINNING_COMBINATIONS[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
};

This function takes the squares array as an argument and checks if any of the winning combinations match the values in the squares array. If there is a winner, it returns the winner (“X” or “O”); otherwise, it returns null.

7.3 Update the handleClick function

Update the handleClick function to check for a winner after each move:

const handleClick = (i) => {
  const squaresCopy = [...squares];
  if (calculateWinner(squaresCopy) || squaresCopy[i]) {
    return;
  }
  squaresCopy[i] = xIsNext ? 'X' : 'O';
  setSquares(squaresCopy);
  setXIsNext(!xIsNext);
};

This code first checks if there is a winner or if the clicked square already has a value. If either condition is true, it returns without making any changes. Otherwise, it updates the squares array with the current player’s mark (“X” or “O”) and toggles the value of xIsNext.

7.4 Update the status variable

Update the status variable to display the winner or the next player’s turn:

let status;
const winner = calculateWinner(squares);
if (winner) {
  status = 'Winner: ' + winner;
} else {
  status = 'Next player: ' + (xIsNext ? 'X' : 'O');
}

This code checks if there is a winner and updates the status variable accordingly. If there is no winner, it displays the next player’s turn.

And that’s it! You should now have a working Tic-Tac-Toe game in React. Try it out in your browser and see if it works as expected. If you encounter any issues, feel free to ask for help or consult the React documentation for further guidance.

Feel free to check out my work here: