Welcome Everybody,
In this article we are going to create a tic-tac-toe app using react and react hooks.
Before we start creating our app, You should familiar
with Javscript, React and React-hooks.
So, Without wasting any time let's move to project setup.
Project setup
In the terminal go to the directory where you want to create you react-app.
and then run the following command.
npx create-react-app tic-tac-toe
For creating our app I am using create-react-app
framework.
If you wanna create it manualy you can do so.
You can delete App.test.js
, setupTests.js
and logo.svg
.
After that clean App.js
like following :
import React from 'react';
import './App.css';
function App() {
return (
<div className="App">
Hello I am react app.
</div>
);
}
export default App;
Run yarn start
or npm start
for running dev server on localhost.
That's enough for project setup. Let's move to next one.
Let's build our app.
Breaking down our app into function.
- First in
src/App.js
, Create a function calledcalculateWinner
to get winner from an array.
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
]
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i]
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a]
}
}
return null
}
We are storing pattern of lines in case of a winner in lines array and if we see any of these pattern at any time of the game we will declare winner.
- For calculating next turn create a function called
calculateNextValue
.
function calculateNextValue(squares) {
return squares.filter(Boolean).length % 2 === 0 ? 'X' : 'O'
}
- then, we can check the current status of game by creating next function.
function calculateStatus(winner, squares, nextValue) {
return winner
? `Winner: ${winner}`
: squares.every(Boolean)
? `Scratch: Cat's game`
: `Next player: ${nextValue}`
}
Now in App.css
let's write some styles for our game-board
.game {
font: 14px 'Century Gothic', Futura, sans-serif;
margin: 20px;
min-height: 260px;
}
.game ol,
.game ul {
padding-left: 30px;
}
.board-row:after {
clear: both;
content: '';
display: table;
}
.status {
margin-bottom: 10px;
}
.restart {
margin-top: 10px;
}
.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
padding: 0;
text-align: center;
width: 34px;
}
.square:focus {
outline: none;
background: #ddd;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
min-width: 190px;
}
Now let's create our game-board in App.js
function Board() {
const [squares, setSquares] = React.useState(Array(9).fill(null))
const nextValue = calculateNextValue(squares)
const winner = calculateWinner(squares)
const status = calculateStatus(winner, squares, nextValue)
function selectSquare(square) {
if (winner || squares[square]) {
return
}
const squaresCopy = [...squares]
squaresCopy[square] = nextValue
setSquares(squaresCopy)
}
function restart() {
setSquares(Array(9).fill(null))
}
function renderSquare(i) {
return (
<button className="square" onClick={() => selectSquare(i)}>
{squares[i]}
</button>
)
}
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className="board-row">
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className="board-row">
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
<button className="restart" onClick={restart}>
restart
</button>
</div>
)
}
Now create a Game()
function and put our Board()
Component
inside it.
function Game() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
</div>
)
}
then render Game()
function inside App()
.
function App() {
return <Game />
}
At the end our App.js
should look like this.
import React from 'react';
import "./App.css";
function Board() {
const [squares, setSquares] = React.useState(Array(9).fill(null))
const nextValue = calculateNextValue(squares)
const winner = calculateWinner(squares)
const status = calculateStatus(winner, squares, nextValue)
function selectSquare(square) {
if (winner || squares[square]) {
return
}
const squaresCopy = [...squares]
squaresCopy[square] = nextValue
setSquares(squaresCopy)
}
function restart() {
setSquares(Array(9).fill(null))
}
function renderSquare(i) {
return (
<button className="square" onClick={() => selectSquare(i)}>
{squares[i]}
</button>
)
}
return (
<div>
<div className="status">{status}</div>
<div className="board-row">
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className="board-row">
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className="board-row">
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
<button className="restart" onClick={restart}>
restart
</button>
</div>
)
}
function Game() {
return (
<div className="game">
<div className="game-board">
<Board />
</div>
</div>
)
}
function calculateStatus(winner, squares, nextValue) {
return winner
? `Winner: ${winner}`
: squares.every(Boolean)
? `Scratch: Cat's game`
: `Next player: ${nextValue}`
}
function calculateNextValue(squares) {
return squares.filter(Boolean).length % 2 === 0 ? 'X' : 'O'
}
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
]
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i]
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a]
}
}
return null
}
function App() {
return <Game />
}
export default App
And that is it. We are done with that.
π‘ Extras
You can save the game's squares to browser localhost by using the following hook:
function useLocalStorageState(
key,
defaultValue = '',
{serialize = JSON.stringify, deserialize = JSON.parse} = {},
) {
const [state, setState] = React.useState(() => {
const valueInLocalStorage = window.localStorage.getItem(key)
if (valueInLocalStorage) {
return deserialize(valueInLocalStorage)
}
return typeof defaultValue === 'function' ? defaultValue() : defaultValue
})
const prevKeyRef = React.useRef(key)
React.useEffect(() => {
const prevKey = prevKeyRef.current
if (prevKey !== key) {
window.localStorage.removeItem(prevKey)
}
prevKeyRef.current = key
window.localStorage.setItem(key, serialize(state))
}, [key, state, serialize])
return [state, setState]
}
export {useLocalStorageState}
Thanks for reading.
Happy coding π.
Top comments (8)
I built my own React-Tac-Toe game a few months ago as part of my own learning effort.
You can access the game and the source on my Github at github.com/jselbie/react-tac-toe
Great work.
Thanks for suggestions.π
This was awesome
πThanks
didn't you copy/paste the one in the react docs ?
bro check your discord.
I just created a lazy version
fabulous-sunflower-848903.netlify....
You know, it does the job
Some comments have been hidden by the post's author - find out more