Friday, September 5, 2025
HomeLanguagesHow to build a Tic-Tac-Toe Game using React Hooks ?

How to build a Tic-Tac-Toe Game using React Hooks ?

React is a frontend open-source JavaScript library, used to build interactive User Interfaces. React is focused on Single Page applications and is more popularly known as a SPA. In this tutorial, we’ll use React and its hooks to build a fun Tic-Tac-Toe application. Before jumping into code make sure the pre-requisites are checked for a better understanding.

Prerequisite:

Modules required:

  • npm
  • React

Creating React App and Setting Up: 

Step 1: You will start a new project using create-react-app so open your terminal and type.

npx create-react-app tic-tac-toe-react

Step 2: Switch to the tic-tac-toe-react folder using the following command.

cd tic-tac-toe-react

Step 3: Change to the src folder and remove the unnecessary stuff using the following command

cd src
rm *

Step 4: Create a css folder in src, which contains the app.css, board.css, index.css, and info.css files.

mkdir css
touch app.css board.css index.css info.css

Step 5: In the src folder, create App.js, Board.js, index.js, and Info.js files.

touch App.js Board.js index.js Info.js

Project Structure: The file structure in the project will look like this.

Example: This example will guide you with code to build a Tic-Tac-Toe game using React Hooks.

index.js: This file links the HTML file and the react code. Edit the index.js file in the following manner: 

Javascript




import React from 'react';
import ReactDOM from 'react-dom';
import './css/index.css';
import App from './App';
 
ReactDOM.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>,
    document.getElementById('root')
);


App.js: This file acts like a base file containing the Info and Board components. Edit the App.js file in the following manner: 

Javascript




// Importing the required components
import Board from './Board';
import Info from "./Info";
 
// Importing the CSS File
import "./css/app.css";
 
// Importing the useState hook
import { useState } from 'react';
 
function App() {
 
    // Creating a reset state, which indicates whether
    // the game should be reset or not
    const [reset, setReset] = useState(false);
 
    // Creating a winner state, which indicates
    // the current winner
    const [winner, setWinner] = useState('');
 
    // Sets the reset property to true
    // which starts the chain
    // reaction of resetting the board
    const resetBoard = () => {
        setReset(true);
    }
 
    return (
        <div className="App">
            {/* Shrinks the popup when there is no winner */}
            <div className={`winner ${winner !== '' ? '' : 'shrink'}`}>
                {/* Display the current winner */}
                <div className='winner-text'>{winner}</div>
                {/* Button used to reset the board */}
                <button onClick={() => resetBoard()}>
                    Reset Board
                </button>
            </div>
            {/* Custom made board component comprising of
            the tic-tac-toe board  */}
            <Board reset={reset} setReset={setReset} winner={winner}
                setWinner={setWinner} />
            <Info />
        </div>
    );
}
 
export default App;


Board.js: This file contains the tic-tac-toe board and the game logic. Edit the Board.js in the following manner: 

Javascript




// Importing the CSS for the board
import "./css/board.css";
 
// Importing the useState hook, useEffect hook and useRef hook
import { useState, useEffect, useRef } from "react";
 
const Board = ({ reset, setReset, winner, setWinner }) => {
 
    // Creating a turn state, which indicates the current turn
    const [turn, setTurn] = useState(0);
 
    // Creating a data state, which contains the
    // current picture of the board
    const [data, setData] = useState(['', '', '', '', '',
        '', '', '', ''])
 
    // Creating a reference for the board
    const boardRef = useRef(null);
 
    // Function to draw on the board
    const draw = (event, index) => {
        // Draws only if the position is not taken
        // and winner is not decided yet
        if (data[index - 1] === '' && winner === '') {
 
            // Draws X if it's player 1's turn else draws O
            const current = turn === 0 ? "X" : "O"
 
            // Updating the data state
            data[index - 1] = current;
 
            //Drawing on the board
            event.target.innerText = current;
 
            // Switching the turn
            setTurn(turn === 0 ? 1 : 0)
        }
    }
 
    // UseEffect hook used to reset the board whenever
    // a winner is decided
    useEffect(() => {
 
        // Clearing the data state
        setData(['', '', '', '', '', '', '', '', '']);
 
        // Getting all the children(cells) of the board
        const cells = boardRef.current.children
 
        // Clearing out the board
        for (let i = 0; i < 9; i++) {
            cells[i].innerText = '';
        }
 
        // Resetting the turn to player 0
        setTurn(0);
 
        // Resetting the winner
        setWinner('');
        setReset(false);
    }, [reset, setReset, setWinner])
 
 
    // useEffect hook used to check for a winner
    useEffect(() => {
 
        // Checks for the win condition in rows
        const checkRow = () => {
            let ans = false;
            for (let i = 0; i < 9; i += 3) {
                ans |= (data[i] === data[i + 1] &&
                    data[i] === data[i + 2] &&
                    data[i] !== '')
            }
            return ans;
        }
 
        // Checks for the win condition in cols
        const checkCol = () => {
            let ans = false;
            for (let i = 0; i < 3; i++) {
                ans |= (data[i] === data[i + 3] &&
                    data[i] === data[i + 6] &&
                    data[i] !== '')
            }
            return ans;
        }
 
        // Checks for the win condition in diagonals
        const checkDiagonal = () => {
            return ((data[0] === data[4] &&
                data[0] === data[8] && data[0] !== '') ||
                (data[2] === data[4] && data[2] === data[6] &&
                    data[2] !== ''));
        }
 
        // Checks if at all a win condition is present
        const checkWin = () => {
            return (checkRow() || checkCol() || checkDiagonal());
        }
 
        // Checks for a tie
        const checkTie = () => {
            let count = 0;
            data.forEach((cell) => {
                if (cell !== '') {
                    count++;
                }
            })
            return count === 9;
        }
 
        // Setting the winner in case of a win
        if (checkWin()) {
            setWinner(turn === 0 ? "Player 2 Wins!" :
                "Player 1 Wins!");
        } else if (checkTie()) {
 
            // Setting the winner to tie in case of a tie
            setWinner("It's a Tie!");
        }
 
    })
 
    return (
        <div ref={boardRef} className="board">
            <div className="input input-1"
                onClick={(e) => draw(e, 1)}></div>
            <div className="input input-2"
                onClick={(e) => draw(e, 2)}></div>
            <div className="input input-3"
                onClick={(e) => draw(e, 3)}></div>
            <div className="input input-4"
                onClick={(e) => draw(e, 4)}></div>
            <div className="input input-5"
                onClick={(e) => draw(e, 5)}></div>
            <div className="input input-6"
                onClick={(e) => draw(e, 6)}></div>
            <div className="input input-7"
                onClick={(e) => draw(e, 7)}></div>
            <div className="input input-8"
                onClick={(e) => draw(e, 8)}></div>
            <div className="input input-9"
                onClick={(e) => draw(e, 9)}></div>
        </div>
    )
}
 
export default Board;


Info.js: This file contains info about the tic-tac-toe game. Edit Info.js in the following manner: 

Javascript




// Importing the css for the info
import "./css/info.css";
 
const Info = () => {
    return (
        <div className="info">
            <div className="player">Player 1: X</div>
            <div className="player">Player 2: O</div>
        </div>
    )
}
 
export default Info;


index.css

CSS




*{
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;   
    box-sizing: border-box; 
  }
   
  body {
    margin: 0;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
      'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
      'Droid Sans', 'Helvetica Neue',
      sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }
   
  code {
    font-family: source-code-pro, Menlo, Monaco,
      Consolas, 'Courier New',
      monospace;
  }


App.css

CSS




 
.App {
    width: 100vw;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    gap: 5vh;
    backdrop-filter: 5px;
    background-color: #101010;
}
 
.winner {
    transition: all ease-in .3s;
    display: flex;
    opacity: 1;
    font-size: 1.5rem;
    font-weight: 600;
    gap: 1vh;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 20vw;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -70%);
    background-color: rgba(195, 141, 158, 0.863);
    backdrop-filter: 5px;
    padding: .5rem;
    padding-bottom: 1rem;
    border-radius: 10%;
}
 
.winner-text {
    padding: .3em 1em .25em;
    font-weight: 600;
    font-size: 2.5rem;
    color: white;
    font-family: 'Bellefair', serif;
    position: relative;
    text-align: center;
    line-height: 1.3;
}
 
.shrink {
    transform: scale(.1);
    opacity: 0;
}
 
button {
    background-color: #111827;
    border: 1px solid transparent;
    border-radius: .75rem;
    box-sizing: border-box;
    color: #FFFFFF;
    cursor: pointer;
    flex: 0 0 auto;
    font-family: "Inter var";
    font-size: 1.125rem;
    font-weight: 600;
    line-height: 1.5rem;
    padding: .75rem 1.2rem;
    text-align: center;
    text-decoration: none #6B7280 solid;
    text-decoration-thickness: auto;
    transition-duration: .2s;
    transition-property: background-color, border-color,
        color, fill, stroke;
    transition-timing-function: cubic-bezier(.4, 0, 0.2, 1);
    user-select: none;
    -webkit-user-select: none;
    touch-action: manipulation;
    width: auto;
}
 
button:hover {
    background-color: #374151;
}
 
button:focus {
    box-shadow: none;
    outline: 2px solid transparent;
    outline-offset: 2px;
}
 
@media (min-width: 768px) {
    button {
        padding: .75rem 1.5rem;
    }
}
 
;


board.css

CSS




:root {
    --board-background: none;
    --border-color: #f6546a;
    --border-thickness: 5px;
}
 
.board {
    width: 30vw;
    height: 50%;
    background-color: var(--board-background);
    display: flex;
    align-items: flex-start;
    flex-direction: row;
    flex-wrap: wrap;
}
 
.input {
    height: 33.33%;
    width: 33.33%;
    display: flex;
    justify-content: center;
    align-items: center;
    color: whitesmoke;
    font-family: 'Bellefair', serif;
    font-style: italic;
    font-weight: 700;
    font-size: 6rem;
}
 
.input-1 {
    border-right: var(--border-thickness) dashed var(--border-color);
    border-bottom: var(--border-thickness) dashed var(--border-color);
}
 
.input-2 {
    border-right: var(--border-thickness) dashed var(--border-color);
    border-bottom: var(--border-thickness) dashed var(--border-color);
}
 
.input-3 {
    border-bottom: var(--border-thickness) dashed var(--border-color);
}
 
.input-4 {
    border-right: var(--border-thickness) dashed var(--border-color);
    border-bottom: var(--border-thickness) dashed var(--border-color);
}
 
.input-5 {
    border-right: var(--border-thickness) dashed var(--border-color);
    border-bottom: var(--border-thickness) dashed var(--border-color);
}
 
.input-6 {
    border-bottom: var(--border-thickness) dashed var(--border-color);
}
 
.input-7 {
    border-right: var(--border-thickness) dashed var(--border-color);
}
 
.input-8 {
    border-right: var(--border-thickness) dashed var(--border-color);
}


info.css

CSS




.info {
    width: 30vw;
    display: flex;
    justify-content: space-evenly;
    align-items: center;
    color: whitesmoke;
}
 
.player {
    border: 2px solid #f6546a;
    border-radius: 5%;
    padding: .5rem 0;
    display: flex;
    font-size: 1.5rem;
    justify-content: center;
    align-items: center;
    width: 10vw;
}


Save all files and start the application by running the following command:

npm start

Output:

Whether you’re preparing for your first job interview or aiming to upskill in this ever-evolving tech landscape, neveropen Courses are your key to success. We provide top-quality content at affordable prices, all geared towards accelerating your growth in a time-bound manner. Join the millions we’ve already empowered, and we’re here to do the same for you. Don’t miss out – check it out now!
RELATED ARTICLES

Most Popular

Dominic
32264 POSTS0 COMMENTS
Milvus
81 POSTS0 COMMENTS
Nango Kala
6634 POSTS0 COMMENTS
Nicole Veronica
11801 POSTS0 COMMENTS
Nokonwaba Nkukhwana
11861 POSTS0 COMMENTS
Shaida Kate Naidoo
6750 POSTS0 COMMENTS
Ted Musemwa
7025 POSTS0 COMMENTS
Thapelo Manthata
6698 POSTS0 COMMENTS
Umr Jansen
6718 POSTS0 COMMENTS