Everyone has played tic-tac-toe at least once, but have you ever created a tic-tac-toe board using React? No? Well then, it's your lucky day! We'll create one from scratch using React, context, useEffects, and some cool features that React provides for us.
Prerequisites
You'll need to have React ready to go before you can build the board, but if it isn't yet installed, don't worry! You just need to install NodeJS and NPM. This is the official NodeJS website, where you can download the best version for you.
Note: If you are using a Mac, I recommend that you install it using Homebrew.
Step 1: Create the App
I'll assume that you now have both NodeJS and NPM installed, so let's go ahead and create our app! Start by opening your terminal and choosing where you want to place your application, then type this command:
npx create-react-app tictactoe |
What does this command do? The npx is the package that usually comes with NodeJS, and it has the create-react-app, which is self-explanatory, but I'll say it anyway: it creates a project with all necessary dependencies for a React application. Finally, the tictactoe is the project name, so you can change it as you want or even create it under a structure like myapps/tictactoe. |
Now that your project is created, you can run NPM start from the terminal and you'll see a screen like this in your localhost:3000:
Step 2: Create Components
For this game, I decided to create components for the board and the cells. You can think of each spot in the tic-tac-toe board as a cell. In order to create these cells, let's create a folder named "components" under src and the files Board.jsx and Cell.jsx under it, as shown below.
Each cell must now know which spot it represents, so let's create a multidimensional array for it. The first cell will be 0-1, the second cell in the same row will be 0-2, the first cell of the second row will be 1-1, and so on:
import React, { useContext } from "react"; |
For the board, we will include 9 cells since we have 9 places on our tic-tac-toe board:
import React from "react"; |
Don't worry about the className for now; we'll create styles for the components at the end.
Let's also create some other components to represent the headers to show who's playing, who wins, and if the game has ended. We can do this by creating components called End.jsx, Header.jsx, Playing.jsx, and Winner.jsx. We'll create code for them pretty soon.
Step 3: Add Some Logic!
Now let's add logic to the main file of the application, the App.js. In the App.js, you will see some code that you can go ahead and wipe out since we'll be creating new code.
Let's start with our imports. In App.js, you can replace the imports with these:
import Board from './components/Board' export const AppContext = createContext(); |
We'll use the useState, createContext, and useEffect in the App.js file.
All the code now should be created inside the function App(). Let's start with the variables that we'll need:
const emptyGame = [["", "", ""], ["", "", ""], ["", "", ""]] // empty board const [cells, setCells] = useState(emptyGame) |
Now let's create the cell behavior. When the user clicks on a cell, this function must be triggered:
function cellClick(row, column) { // if it reaches here means that the click is valid // Change the current player if (currentChar == X) { |
Now we have the function, but we aren't triggering it anywhere. We have to add it in the Cell.jsx file, and in order to do this, we'll use the useContext that we have imported because Context is a simple way to pass variables through components.
Replace the return (...) as shown below:
return ( |
AppContext is the name we defined for the context right after the imports. We have to wrap in it the components that will have access to this context. Last but not least, we pass in the value, the functions, and the variables we want to give access to. You may have noticed that I added a button to reset the board. I won't show it here because I want to challenge you to create it on your own, but you can reference my GitHub and view the whole project there with this function if you want. |
In the Cell.jsx file, we'll now have to get the context. To do so, we'll need to import the context and use it as shown below in the final version of Cell.jsx:
import React, { useContext } from "react"; // active and winner are used for style purposes |
We are almost there, so stick with me!
Let's go back to our App.js file and create the functions to end the game:
// instead of checking cell[0][0] is equal to cell[0][1] and so on, // I created a function to check all values in the same row // and another function to check all values in the same column function isGameOver() { // this is going to check if we still have empty spots // it receives the row and checks if all values in that row are the equal // it receives the column and checks if all values in that column are the equal |
Are we missing something?
Yes, we are! We're missing the files End.jsx, Header.jsx, Playing.jsx, and Winner.jsx, so let's code them!
Header:
import React, { useContext, useState } from "react"; // This is equivalent of a ternary if to conditionally show something // { !winner && !gameOver && <Playing /> } // it will show the Playing component if has no winner and the have not ended |
Winner:
import React, { useContext, useState } from "react"; |
End:
import React from "react"; |
Playing:
import React, { useContext, useState } from "react"; |
Everything looks fine, but when and where do we call the isGameOver() function?
Well, let's create an effect for that!
The useEffect applies some function every time the "spied" object changes.
Create the following code inside the function App():
useEffect(function() { |
This will run the isGameOver() function every time we change the "cells" constant.
Step 4: Add Styling
Our project is functional, but it does not look great. Just run it and you'll see how awful it is. Let's fix this!
First, create a folder named "style" under the src folder, then create three files for the board, cell, and header.
You can tweak it as you want, but here's how I did it:
board.css:
.board { |
cell.css:
.cell { |
header.css:
.header { |
Now our game not only works well, but it also looks great!
Conclusion
You can find the complete project on my GitHub page.
There are thousands of ways to create a tic-tac-toe game, so tell me: what would you do differently from this approach? How would you improve this code?
Author
Diego Zanivan
Diego Zanivan is a Back-End Developer at Avenue Code. He's a cutting-edge technology enthusiast who's been passionate about developing awesome tools for over 15 years. Diego usually works with PHP and Java but also fills in as the Front-End guy sometimes.