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 version 12.2.0 or higher is installed on your machine. You can install the latest version of Node.js here Node.js.
- 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:
npm create vite@latest
After the script finishes, you will be prompted to enter a project name: Type in tic-tac-toe
Project name:tic-tac-toe
After entering your project name, Vite will prompt you to select a framework:
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:
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:
Done. Now run:
cd tic-tac-toe
npm install
npm run dev
Navigate to your project folder as directed:
$ cd tic-tac-toe
Then install npm on the project:
npm install
Step 2 — Starting the Development Server
npm run dev
It would run your project in development mode and get you stared
> 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:
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
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
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
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
import Board from "./Board";
const TicTacToe = () => {
return (
<div>
<Board />
</div>
);
};
export default TicTacToe;
import Square.jsx into Board.jsx
// Board.jsx
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
import { useState } from "react";
const [square, setSquare] = useState(Array(9).fill(null));
Pass down square as a prop to Board component
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
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
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
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
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
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
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
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;
Step 6 — Styling the strike div and hover effect on each sqaure
* {
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
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
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
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
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;
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
* {
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!
Top comments (0)