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 : 100 vw; height : 100 vh; display : flex; justify- content : center ; align-items: center ; flex- direction : column; gap: 5 vh; backdrop-filter: 5px ; background-color : #101010 ; } .winner { transition: all ease-in . 3 s; display : flex; opacity: 1 ; font-size : 1.5 rem; font-weight : 600 ; gap: 1 vh; flex- direction : column; justify- content : center ; align-items: center ; width : 20 vw; position : absolute ; top : 50% ; left : 50% ; transform: translate( -50% , -70% ); background-color : rgba( 195 , 141 , 158 , 0.863 ); backdrop-filter: 5px ; padding : . 5 rem; padding-bottom : 1 rem; border-radius: 10% ; } .winner-text { padding : . 3em 1em . 25em ; font-weight : 600 ; font-size : 2.5 rem; 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: . 75 rem; box-sizing: border-box; color : #FFFFFF ; cursor : pointer ; flex: 0 0 auto ; font-family : "Inter var" ; font-size : 1.125 rem; font-weight : 600 ; line-height : 1.5 rem; padding : . 75 rem 1.2 rem; text-align : center ; text-decoration : none #6B7280 solid ; text-decoration-thickness: auto ; transition-duration: . 2 s; 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 : . 75 rem 1.5 rem; } } ; |
board.css
CSS
:root { --board- background : none ; -- border-color : #f6546a ; --border-thickness: 5px ; } .board { width : 30 vw; 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 : 6 rem; } .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 : 30 vw; display : flex; justify- content : space-evenly; align-items: center ; color : whitesmoke; } .player { border : 2px solid #f6546a ; border-radius: 5% ; padding : . 5 rem 0 ; display : flex; font-size : 1.5 rem; justify- content : center ; align-items: center ; width : 10 vw; } |
Save all files and start the application by running the following command:
npm start
Output: