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 componentsimport Board from './Board';import Info from "./Info";// Importing the CSS Fileimport "./css/app.css";// Importing the useState hookimport { 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 boardimport "./css/board.css";// Importing the useState hook, useEffect hook and useRef hookimport { 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 infoimport "./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:




