Building a Tic Tac Toe App with React.js: A Step-by-Step Guide 2024
Published: 2024-02-02 13:01:49

## Introduction:
Welcome to our step-by-step guide on creating a Tic Tac Toe game using React.js! React.js is a powerful JavaScript library for building user interfaces, and it's an excellent choice for creating interactive and dynamic web applications. In this tutorial, we'll walk you through the process of building a simple Tic Tac Toe game to help you understand the fundamentals of React.js.

## Prerequisites:
To follow this tutorial, you will need the following:

- [Node.js](https://nodejs.org/en/) version 12.2.0 or higher is installed on your machine. You can install the latest version of Node.js here [Node.js](https://nodejs.org/en/).
- Familiarity with HTML, CSS, and modern JavaScript. It also helps to know the modern JS used in React.
- A foundational knowledge of React.

**Step 1 — Creating a Vite Project**

In this step, you will create a new React project using the Vite tool from the command line. You will use the npm package manager to install and run the scripts.

Run the following command in your terminal to scaffold a new Vite project:

```javascript
npm create vite@latest
```

After the script finishes, you will be prompted to enter a project name: Type in **tic-tac-toe**

```javascript
Project name:tic-tac-toe
```
After entering your project name, Vite will prompt you to select a framework:

```javascript
Output
Select a framework: » - Use arrow keys. Return to submit.
Vanilla
Vue
> React
Preact
Lit
Svelte
Others
```
Vite allows you to bootstrap a range of project types, not just React. Currently, it supports React, Preact, Vue, Lit, Svelte, and vanilla JavaScript projects.

Use your keyboard arrow key to select React.

After selecting the React framework, Vite will prompt you to choose the language type. You can use JavaScript or TypeScript to work on your project.

Use your arrow keys to select JavaScript:

``` javascript
Select a variant: » - Use arrow keys. Return to submit.
> JavaScript
TypeScript
JavaScript + SWC
TypeScript + SWC
```
After setting up the framework, you will see an output that the project has been scaffolded. Vite will then instruct you to install dependencies using npm:

```javascript
Done. Now run:
 cd tic-tac-toe
 npm install
 npm run dev
```
Navigate to your project folder as directed:

```javascript
$ cd tic-tac-toe
```

Then install npm on the project: 
```javascript
 npm install
```

**Step 2 — Starting the Development Server**

```javascript
 npm run dev
```
It would run your project in development mode and get you stared
```javascript
> tic-tac-toe@0.0.0 dev
> vite

  VITE v5.0.12  ready in 310 ms
  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
```
Next, open your browser and visit http://localhost:5173/. The default React project will be running on port 5173:


![Vite Boilerplate app.jsx page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kkfumk4ammx37p2j730a.png)

**Step 3 — Create TicTactoe, Board and Square Components**

In the "src" folder, create a new file named "TicTactoe.jsx." This component will represent the Tic Tac Toe game board and hold all the logic of the game. Define the initial structure of the Tic Tac Toe and render it using React.

// TicTacToe.jsx
``` Javascript
const TicTacToe = () => {
  return (
    <div>
     TicTacToe
    </div>
  );
};
export default TicTacToe;
```
In the "src" folder, create a new file named "Board.jsx."This would all the squares and the UI structure of the game
// Board.jsx
``` Javascript
const Board = () => {
  return (
    <div>
      Board
    </div>
  );
};
export default Board;
```
In the "src" folder, create a new file named "Square.jsx." This component would represent every individual square in the game board

// Square.jsx
``` Javascript
const Square = () => {
  return (
    <div>
    Square
    </div>
  );
};
export default Square;
```
**Step 4 — Connect all the components and create an array to track value and pass down as props to square component with value**

import board.jsx into TicTacToe.jsx

// TicTacToe.jsx
``` Javascript

import Board from "./Board";
const TicTacToe = () => {
  return (
    <div>
     <Board />
    </div>
  );
};
export default TicTacToe;
```
import Square.jsx into Board.jsx

// Board.jsx
``` Javascript
import Square from "./Square";
const Board = () => {
  return (
    <div>
     <Square />
    </div>
  );
};
export default Board;
```
// TicTacToe.jsx
We would create an array and track the state with useState. import useState from react and create an array with the initial value of null to hold all the values of 9 squares

``` Javascript
import { useState } from "react";
const [square, setSquare] = useState(Array(9).fill(null));
```
Pass down **square** as a prop to **Board** component 

``` Javascript
import Board from "./Board";
import { useState } from "react";

const TicTacToe = () => {
const [square, setSquare] = useState(Array(9).fill(null));
  return (
    <div>
     <Board square={square} />
    </div>
  );
};
export default TicTacToe;
```
Create a grid of 9 squares with Tailwind CSS and pass down the square state as custom values from index 0 - 8. Remember JavaScript arrays are zero-indexed
 
// Board.jsx
``` Javascript
import Square from "./Square";
const Board = ({square}) => {
  return (
   <div className="grid grid-cols-3 grid-rows-3 relative">
    <Square value={square[0]} />
    <Square value={square[1]}/>
    <Square value={square[2]}/>
    <Square value={square[3]}/>
    <Square value={square[4]}/>
    <Square value={square[5]}/>
    <Square value={square[6]}/>
    <Square value={square[7]}/>
    <Square value={square[8]}/>
   </div>
  );
};
export default Board;
```
In the square component use value as the content of each square
// Square.jsx
``` Javascript
const Square = ({value}) => {
  return (
    <div>
      {value}
    </div>
  );
};
export default Square;
```
**Step 5 — Track each player Turn with useState and value X and O. Create a click event to change the state of each square on player click**

``` Javascript
import Board from "./Board";
import { useState } from "react";

const TicTacToe = () => {
const [square, setSquare] = useState(Array(9).fill(null));
const [player, setPlayer] = useState("X");

const handleSquareClick = (index) => {
   // Return if square already contents a value
    if (square[index] !== null) return;
   // Create a new array to add each square index 
    const newSquare = [...square];
    newSquare[index] = player;
   // set square array to the new Array
    setSquare(newSquare);
   // switch between X and O based on player turn
    setPlayer(player === "X" ? "O" : "X");
  };

  return (
    <div>
     <Board square={square} />
    </div>
  );
};
export default TicTacToe;
```
Pass down the handleSquareClick to Board component

 ``` Javascript
import Board from "./Board";
import { useState } from "react";

const TicTacToe = () => {
const [square, setSquare] = useState(Array(9).fill(null));
const [player, setPlayer] = useState("X");

const handleSquareClick = (index) => {
   // Return if square already contents a value
    if (square[index] !== null) return;
   // Create a new array to add each square index 
    const newSquare = [...square];
    newSquare[index] = player;
   // set square array to the new Array
    setSquare(newSquare);
   // switch between X and O based on player turn
    setPlayer(player === "X" ? "O" : "X");
  };

  return (
    <div>
     <Board square={square} squareClick={handleSquareClick} />
    </div>
  );
};
export default TicTacToe;
```

Pass down the squareClick function to square component 

// Board.jsx
``` Javascript
import Square from "./Square";
const Board = ({square, squareClick}) => {
  return (
   <div className="grid grid-cols-3 grid-rows-3 relative">
    <Square value={square[0]} onClick={() => squareClick(0)}/>
    <Square value={square[1]} onClick={() => squareClick(1)}/>
    <Square value={square[2]} onClick={() => squareClick(2)}/>
    <Square value={square[3]} onClick={() => squareClick(3)}/>
    <Square value={square[4]} onClick={() => squareClick(4)}/>
    <Square value={square[5]} onClick={() => squareClick(5)}/>
    <Square value={square[6]} onClick={() => squareClick(6)}/>
    <Square value={square[7]} onClick={() => squareClick(7)}/>
    <Square value={square[8]} onClick={() => squareClick(8)}/>
   </div>
  );
};
export default Board;
```
// Square.jsx
```Javascript
const Square = ({value,onClick}) => {
  return (
    <div onClick={onClick}>
      {value}
    </div>
  );
};
```
**Step 6 — Create a Strike div in Board to update the board when we have a winning pattern**

// Board.jsx
``` Javascript
import Square from "./Square";
const Board = ({square, squareClick}) => {
  return (
<div className="flex flex-col">
   <div className="grid grid-cols-3 grid-rows-3 relative">
    <Square value={square[0]} onClick={() => squareClick(0)}/>
    <Square value={square[1]} onClick={() => squareClick(1)}/>
    <Square value={square[2]} onClick={() => squareClick(2)}/>
    <Square value={square[3]} onClick={() => squareClick(3)}/>
    <Square value={square[4]} onClick={() => squareClick(4)}/>
    <Square value={square[5]} onClick={() => squareClick(5)}/>
    <Square value={square[6]} onClick={() => squareClick(6)}/>
    <Square value={square[7]} onClick={() => squareClick(7)}/>
    <Square value={square[8]} onClick={() => squareClick(8)}/>
   </div>
   <div className={`absolute w-full bg-orange-600 z-40`}></div>
  </div>
  );
};
export default Board;
```

![strike](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ss06kaehl06lwtanmlck.png)

**Step 6 — Styling the strike div and hover effect on each sqaure**

``` CSS
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-size: 10px;
}
body {
  background-color: #f5f5f5;
}

.strike-row-1 {
  width: 100%;
  height: 4px;
  top: 15%;
}
.strike-row-2 {
  width: 100%;
  height: 4px;
  top: 48%;
}
.strike-row-3 {
  width: 100%;
  height: 4px;
  top: 83%;
}
.strike-column-1 {
  height: 100%;
  width: 4px;
  left: 15%;
}
.strike-column-2 {
  height: 100%;
  width: 4px;
  left: 48%;
}
.strike-column-3 {
  height: 100%;
  width: 4px;
  left: 83%;
}
.strike-diagonal-1 {
  width: 90%;
  height: 4px;
  top: 50%;
  left: 5%;
  transform: skewY(45deg);
}
.strike-diagonal-2 {
  width: 90%;
  height: 4px;
  top: 50%;
  left: 5%;
  transform: skewY(-45deg);
}

.x-hover:hover::after {
  content: "X";
  opacity: 0.4;
}

.o-hover:hover::after {
  content: "O";
  opacity: 0.4;
}
```

Our board would look like this 

// Board.jsx
``` Javascript
import Square from "./Square";
const Board = ({square, squareClick,strikeClass}) => {
  return (
<div className="flex flex-col">
   <div className="grid grid-cols-3 grid-rows-3 relative">
    <Square value={square[0]} onClick={() => squareClick(0)}/>
    <Square value={square[1]} onClick={() => squareClick(1)}/>
    <Square value={square[2]} onClick={() => squareClick(2)}/>
    <Square value={square[3]} onClick={() => squareClick(3)}/>
    <Square value={square[4]} onClick={() => squareClick(4)}/>
    <Square value={square[5]} onClick={() => squareClick(5)}/>
    <Square value={square[6]} onClick={() => squareClick(6)}/>
    <Square value={square[7]} onClick={() => squareClick(7)}/>
    <Square value={square[8]} onClick={() => squareClick(8)}/>
   </div>
// The strikeClass would come from Tic-tac-toe component 
   <div className={`absolute w-full bg-orange-600 z-40 ${strikeClass}`}></div>
  </div>
  );
};
export default Board;
```

**Step 7 Handling the winning combination and using the strikeClass based on the winning position**


``` Javascript
import Board from "./Board";
import { useState } from "react";

// Winner function 
const checkWinner = (square, setStrikeClass, setWinner) => {
// Multiple winning combination based on the position of the square and value
  const winningCombos = [
    { combo: [0, 1, 2], strikeClass: "strike-row-1" }, // top row
    { combo: [3, 4, 5], strikeClass: "strike-row-2" }, // middle row
    { combo: [6, 7, 8], strikeClass: "strike-row-3" }, // bottom row
    { combo: [0, 4, 8], strikeClass: "strike-diagonal-1" }, // top left to bottom right
    { combo: [2, 4, 6], strikeClass: "strike-diagonal-2" }, // top right to bottom left
    { combo: [0, 3, 6], strikeClass: "strike-column-1" }, // left column
    { combo: [1, 4, 7], strikeClass: "strike-column-2" }, // middle column
    { combo: [2, 5, 8], strikeClass: "strike-column-3" }, // right column
  ];
  for (const { combo, strikeClass } of winningCombos) {
    const [a, b, c] = combo;
    if (cells[a] && cells[a] === cells[b] && cells[a] === cells[c]) {
      setStrikeClass(strikeClass);
      if (cells[a] === "X") {
        setWinner("X");
      } else {
        setWinner("O");
      }
      return;
    }
  }
  const isDraw = square.every((square) => cell !== null);
  setWinner(isDraw ? "draw" : null);
};

const TicTacToe = () => {
const [square, setSquare] = useState(Array(9).fill(null));
const [player, setPlayer] = useState("X");
const [strikeClass, setStrikeClass] = useState("hidden");
const [winner, setWinner] = useState("");

const handleSquareClick = (index) => {
   // Return if square already contents a value
    if (square[index] !== null) return;
   // Create a new array to add each square index 
    const newSquare = [...square];
    newSquare[index] = player;
   // set square array to the new Array
    setSquare(newSquare);
   // switch between X and O based on player turn
    setPlayer(player === "X" ? "O" : "X");
  };

   // Track the winner
  useEffect(() => {
    checkWinner(square, setStrikeClass, setWinner);
  }, [square]);

  return (
    <div>
      <Board
        cells={cells}
        onClick={handleCellClick}
        strikeClass={strikeClass}
        player={player}
        winner={winner}
      />
    </div>
  );
};
export default TicTacToe;
```

## The entire code for each component and styling
 
**// TicTacToe.jsx**

```javascript 

import { useState, useEffect } from "react";
import Board from "./Board";

const checkWinner = (square, setStrikeClass, setWinner) => {
  const winningCombos = [
    { combo: [0, 1, 2], strikeClass: "strike-row-1" }, // top row
    { combo: [3, 4, 5], strikeClass: "strike-row-2" }, // middle row
    { combo: [6, 7, 8], strikeClass: "strike-row-3" }, // bottom row
    { combo: [0, 4, 8], strikeClass: "strike-diagonal-1" }, // top left to bottom right
    { combo: [2, 4, 6], strikeClass: "strike-diagonal-2" }, // top right to bottom left
    { combo: [0, 3, 6], strikeClass: "strike-column-1" }, // left column
    { combo: [1, 4, 7], strikeClass: "strike-column-2" }, // middle column
    { combo: [2, 5, 8], strikeClass: "strike-column-3" }, // right column
  ];
  for (const { combo, strikeClass } of winningCombos) {
    const [a, b, c] = combo;
    if (square[a] && square[a] === square[b] && square[a] === square[c]) {
      setStrikeClass(strikeClass);
      if (square[a] === "X") {
        setWinner("X");
      } else {
        setWinner("O");
      }
      return;
    }
  }
  const isDraw = square.every((square) => square !== null);
  setWinner(isDraw ? "draw" : null);
};

const TicTacToe = () => {
  const [square, setSquare] = useState(Array(9).fill(null));
  const [player, setPlayer] = useState("X");
  const [strikeClass, setStrikeClass] = useState("hidden");
  const [winner, setWinner] = useState("");

  const handleCellClick = (index) => {
    if (winner) return;
    if (square[index] !== null) return;
    const newSquare = [...square];
   newSquare[index] = player;
   setSquare(newSquare);
    setPlayer(player === "X" ? "O" : "X");
  };

  useEffect(() => {
    checkWinner(square, setStrikeClass, player, setWinner);
  }, [square]);

  const handleReset = () => {
    if (!winner && winner !== "draw") return;
    setSquare(Array(9).fill(null));
    setPlayer("X");
    setStrikeClass("hidden");
    setWinner("");
  };

  return (
    <div className="flex justify-center items-center mt-40">
      <Board
        square={square}
        onClick={handleCellClick}
        strikeClass={strikeClass}
        player={player}
        winner={winner}
        reset={handleReset}
      />
    </div>
  );
};

export default TicTacToe;
```
// Board.jsx
```javascript

import React from "react";
import Square from "./Square";

const Board = ({ square, onClick, strikeClass, player, winner, reset }) => {
  return (
    <div className="flex flex-col">
      <div className="grid grid-cols-3 grid-rows-3 relative">
        <Square
          value={square[0]}
          onClick={() => onClick(0)}
          className="border-r-8 border-b-8"
          player={player}
        />
        <Square
          value={square[1]}
          onClick={() => onClick(1)}
          className="border-r-8 border-b-8"
          player={player}
        />
        <Cell
          value={square[2]}
          onClick={() => onClick(2)}
          className="border-b-8"
          player={player}
        />
        <Square
          value={square[3]}
          onClick={() => onClick(3)}
          className="border-r-8 border-b-8"
          player={player}
        />
        <Square
          value={square[4]}
          onClick={() => onClick(4)}
          className="border-r-8 border-b-8"
          player={player}
        />
        <Square
          value={square[5]}
          onClick={() => onClick(5)}
          className="border-b-8"
          player={player}
        />
        <Square
          value={square[6]}
          onClick={() => onClick(6)}
          className="border-r-8"
          player={player}
        />
        <Square
          value={square[7]}
          onClick={() => onClick(7)}
          className="border-r-8"
          player={player}
        />
        <Square
          value={square[8]}
          onClick={() => onClick(8)}
          className=""
          player={player}
        />
        <div
          className={`absolute w-full bg-orange-600 z-40 ${strikeClass}`}></div>
      </div>
      {winner && (
        <div className="flex justify-center mt-16 border-2 p-4">
          <h3 className="text-4xl">
            {winner === "draw" ? "Its a Tie" : `Player ${winner} Wins!`}
          </h3>
        </div>
      )}
      <div className="flex justify-center mt-16 border-2 p-4 text-4xl">
        <button onClick={reset}>Reset</button>
      </div>
    </div>
  );
};

export default Board;
```

```javascript
import React from "react";

const Square = ({ onClick, value, className, player }) => {
  let hoverClass = null;
  if (value == null && player != null) {
    hoverClass = `${player.toLowerCase()}-hover`;
  }
  return (
    <button
      onClick={onClick}
      className={`w-28 h-28 text-5xl ${className} ${hoverClass}`}>
      {value}
    </button>
  );
};

export default Square;

```
CSS 

```CSS

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-size: 10px;
}
body {
  background-color: #f5f5f5;
}

.strike-row-1 {
  width: 100%;
  height: 4px;
  top: 15%;
}
.strike-row-2 {
  width: 100%;
  height: 4px;
  top: 48%;
}
.strike-row-3 {
  width: 100%;
  height: 4px;
  top: 83%;
}
.strike-column-1 {
  height: 100%;
  width: 4px;
  left: 15%;
}
.strike-column-2 {
  height: 100%;
  width: 4px;
  left: 48%;
}
.strike-column-3 {
  height: 100%;
  width: 4px;
  left: 83%;
}
.strike-diagonal-1 {
  width: 90%;
  height: 4px;
  top: 50%;
  left: 5%;
  transform: skewY(45deg);
}
.strike-diagonal-2 {
  width: 90%;
  height: 4px;
  top: 50%;
  left: 5%;
  transform: skewY(-45deg);
}

.x-hover:hover::after {
  content: "X";
  opacity: 0.4;
}

.o-hover:hover::after {
  content: "O";
  opacity: 0.4;
}
```

## Conclusion:
Congratulations! You've successfully created a simple Tic Tac Toe game using React.js. This project covers the basics of React components, state management, and event handling. Feel free to enhance your game by adding features like a game history log, or styling improvements. Happy coding!