To build a Tic-Tac-Toe using react Hooks include the interactive components that represent the board, the signs, and at last the winner of the game.
Preview of final output: Let us have a look at how the final application will look like.
Prerequisite of Tic-Tac-Toe Game:
Approach:
To create a Tic-Tac-Toe Game in React js will use the React functional components along with React JS hooks to enable the interaction and store moves. After each move, the component will switch between players and mark X or 0 on selected part the board. Style the game board using CSS for a better appearance.
Steps to Create React Application and Install required modules:
Step 1: Use this command in the terminal to initialize the react project.
npx create-react-app tic-tac-toe-react
Step 2: Switch to the tic-tac-toe-react directory using the following command.
cd tic-tac-toe-react
Project Structure:
Example: This example implement react components and css to build a Tic-Tac-Toe game using React Hooks.
// Filename - App.js // Importing the required components import Board from "./Board" ;
import Info from "./Info" ;
// Importing the CSS File import "./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;
|
// Filename - Board.js // 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;
|
// Filename - Info.js // 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 */ * { -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 ;
} |
/* Filename - App.css */ @import url (
); .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 0.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 : 0.5 rem;
padding-bottom : 1 rem;
border-radius: 10% ;
} .winner-text { padding : 0.3em 1em 0.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( 0.1 );
opacity: 0 ;
} button { background-color : #111827 ;
border : 1px solid transparent ;
border-radius: 0.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 : 0.75 rem 1.2 rem;
text-align : center ;
text-decoration : none #6b7280 solid ;
text-decoration-thickness: auto ;
transition-duration: 0.2 s;
transition-property: background-color, border-color,
color, fill, stroke;
transition-timing-function: cubic-bezier(
0.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 : 0.75 rem 1.5 rem;
}
} |
/* Filename - css/info.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 : 0.5 rem 0 ;
display : flex;
font-size : 1.5 rem;
justify- content : center ;
align-items: center ;
width : 10 vw;
} |
/* Filename - css/board.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);
} |
Steps to Run the Application:Use this command in the terminal inside the project directory.
npm start
Output: This output will be visible on http://localhost:3000/ on the browser window.