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

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

By: Ajdin Imsirovic 21 March 2023

Let’s code a simple Tic-Tac-Toe game in React!

Here’s a step-by-step guide to coding Tic-Tac-Toe, using React hooks:

1. Install and setup the starter app

First, make sure you have a basic understanding of HTML, CSS, and JavaScript. React is a JavaScript library, so you’ll need to have some knowledge of JavaScript to create a React application.

Create a new React project using the create-react-app command. Open your terminal and navigate to the directory where you want to create your project. Type the following command:

npx create-react-app tic-tac-toe

This will create a new React project called “tic-tac-toe” in a directory of the same name.

2. Start adding the game’s code to App.js

Open the project in your code editor. You should see a file called App.js in the src directory. This is the main component of your React application.

Add some code to the App.js file to create the Tic-Tac-Toe game.

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

function App() {
  const [board, setBoard] = useState(Array(9).fill(null));
  const [player, setPlayer] = useState('X');

  const handleClick = (index) => {
    const newBoard = [...board];
    if (board[index] === null) {
      newBoard[index] = player;
      setBoard(newBoard);
      setPlayer(player === 'X' ? 'O' : 'X');
    }
  };

  const renderSquare = (index) => {
    return (
      <button className="square" onClick={() => handleClick(index)}>
        {board[index]}
      </button>
    );
  };

  const status = `Next player: ${player}`;

  return (
    <div className="game">
      <div className="game-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 className="game-info">{status}</div>
    </div>
  );
}

export default App;

3. Add some styling

Save the App.js file and open the App.css file. Add some CSS to style your Tic-Tac-Toe game.

.game {
  display: flex;
  flex-direction: row;
  margin: 20px;
}

.game-board {
  margin-right: 20px;
}

.board-row {
  display: flex;
  flex-direction: row;
}

.square {
  background: #fff;
  border: 1px solid #999;
  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;
}

.game-info {
  margin-top: 20px;
}

4. Run the app server to check the progress

Start the React development server by running the following command in your terminal:

npm start

This will start the server and open your Tic-Tac-Toe game in your default web browser.

5. Test your game’s functionality

Test your game by clicking on the squares. You should see an X or an O appear in each square when you click on it. The player’s turn should also change after each move.

6. Determine the winner

Implement the logic to determine the winner of the game. You can do this by checking the board array for three X’s or three O’s in a row.

const 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;
};

7. Check for a winner on each click

Modify the handleClick function to check for a winner after each move. If there is a winner, display a message indicating who won. If there is no winner and all squares are filled, display a message indicating that the game is a draw.

const handleClick = (index) => {
  const newBoard = [...board];
  if (board[index] === null && !winner) {
    newBoard[index] = player;
    setBoard(newBoard);
    const nextPlayer = player === 'X' ? 'O' : 'X';
    setPlayer(nextPlayer);
    const winner = calculateWinner(newBoard);
    if (winner) {
      setWinner(winner);
    } else if (!newBoard.includes(null)) {
      setWinner('draw');
    }
  }
};

let status;
if (winner) {
  status = `Winner: ${winner}`;
} else {
  status = `Next player: ${player}`;
}

8. Add a reset button for a new game

Add a reset button to reset the game.

const resetGame = () => {
  setBoard(Array(9).fill(null));
  setPlayer('X');
  setWinner(null);
};

return (
  <div className="game">
    <div className="game-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 className="game-info">{status}</div>
    {winner && (
      <button className="reset-button" onClick={resetGame}>
        Reset
      </button>
    )}
  </div>
);

9. Style the reset button

Finally, style the reset button.

.reset-button {
  background: #fff;
  border: 1px solid #999;
  font-size: 16px;
  font-weight: bold;
  margin-top: 20px;
  padding: 10px 20px;
}

That’s it! You now have a basic Tic-Tac-Toe game in React. Of course, there’s plenty of room for improvement.

10. Review of the completed app

Here’s the completed App.js file:

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

const 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;
};

const App = () => {
  const [board, setBoard] = useState(Array(9).fill(null));
  const [player, setPlayer] = useState('X');
  const [winner, setWinner] = useState(null);

  const handleClick = (index) => {
    const newBoard = [...board];
    if (board[index] === null && !winner) {
      newBoard[index] = player;
      setBoard(newBoard);
      const nextPlayer = player === 'X' ? 'O' : 'X';
      setPlayer(nextPlayer);
      const winner = calculateWinner(newBoard);
      if (winner) {
        setWinner(winner);
      } else if (!newBoard.includes(null)) {
        setWinner('draw');
      }
    }
  };

  const renderSquare = (index) => {
    return (
      <button className="square" onClick={() => handleClick(index)}>
        {board[index]}
      </button>
    );
  };

  let status;
  if (winner) {
    status = `Winner: ${winner}`;
  } else {
    status = `Next player: ${player}`;
  }

  const resetGame = () => {
    setBoard(Array(9).fill(null));
    setPlayer('X');
    setWinner(null);
  };

  return (
    <div className="game">
      <div className="game-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 className="game-info">{status}</div>
      {winner && (
        <button className="reset-button" onClick={resetGame}>
          Reset
        </button>
      )}
    </div>
  );
};

export default App;

Here’s the completed App.css file:

.game {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: Arial, sans-serif;
  margin-top: 50px;
}

.game-board {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.board-row {
  display: flex;
}

.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;
  cursor: pointer;
}

.square:focus {
  outline: none;
}

.reset-button {
  margin-top: 20px;
  padding: 10px;
  background-color: #4CAF50;
  color: #fff;
  border: none;
  border-radius: 5px;
  font-size: 16px;
  cursor: pointer;
}

.reset-button:hover {
  background-color: #3e8e41;
}

This file contains styles for the game container, the game board, the square buttons, and the reset button. The styles are pretty basic, but they’re enough to make the game look decent. Feel free to tweak them as you see fit!

In the next article, I’ll show you an alternative way of coding the Tic-Tac-Toe game.

Feel free to check out my work here: