DEV Community

Cover image for Let's create a tic-tac-toe with React.
Navraj Sandhu
Navraj Sandhu

Posted on

Let's create a tic-tac-toe with React.

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 
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 called calculateWinner 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
}

Enter fullscreen mode Exit fullscreen mode

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'
}

Enter fullscreen mode Exit fullscreen mode
  • 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}`
}

Enter fullscreen mode Exit fullscreen mode

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;
}

Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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>
  )
}

Enter fullscreen mode Exit fullscreen mode

then render Game() function inside App().


function App() {
  return <Game />
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

Thanks for reading.

Happy coding ๐Ÿ˜.

Top comments (8)

Collapse
 
jselbie profile image
John Selbie • Edited

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

Collapse
 
asyncnavi profile image
Navraj Sandhu

Great work.

Collapse
 
asyncnavi profile image
Navraj Sandhu

Thanks for suggestions.๐Ÿ’

Collapse
 
matengodev profile image
Davis O Matengo

This was awesome

Collapse
 
asyncnavi profile image
Navraj Sandhu

๐Ÿ™Thanks

Collapse
 
scr2em profile image
Info Comment hidden by post author - thread only accessible via permalink
Mohamed Abdelgwad

didn't you copy/paste the one in the react docs ?

Collapse
 
darshkul24 profile image
Darsh

bro check your discord.

Collapse
 
som31 profile image
Some1 • Edited

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