Section 6 - Advanced State

Tic Tac Toe - Part 1

  1. Remember we built a click counter earlier? Now it’s time for the second project EVERYONE learning React has to write - Tic Tac Toe! We will first build a basic version of Tic Tac Toe, then we’ll improve upon it. This section is divided into 3 parts, so feel free to take a break in between! Let’s get started!

  1. Let’s once again clear up our App.js so we can start off fresh:

import React, { Component } from 'react';

class App extends Component {

  render() {

  }

}

export default App;

  1. We’ll need 9 squares with similar functionalities, so let’s write one Square component and render it 9 times! Create a new file called Square.js and put in the usual React skeleton:

import React, { Component } from 'react';

class Square extends Component {

  render() {

  }

}

export default App;

  1. Let’s first create a square in JSX and add some styles to it! The following step is just HTML and CSS, and mostly has nothing to do with React:

import React, { Component } from 'react';

const style = {

  backgroundColor: "white",

  border: "black solid 2px",

  textAlign: "center",

  fontSize: "20px",

  width: "40px",

  height: "40px",

  margin: -1,

  display: "inline-block",

  verticalAlign: "top",

}

class Square extends Component {

  render() {

    return (

      <div style={style}>X</div>

    )

  }

}

export default Square;

  1. Cool! Let’s go back to App.js. We’ll import the Square component, and render nine of them in our App.js:

import React, { Component } from 'react';

import Square from './Square';

import "./App.css";

class App extends Component {

  render() {

    return (

      <div>

        <div><Square /><Square /><Square /></div>

        <div><Square /><Square /><Square /></div>

        <div><Square /><Square /><Square /></div>

      </div>

    )

  }

}

export default App;

Perfect!

  1. Now, let’s head back to Square.js. Pretend two people are playing on the same computer. Let’s make it so that each square can display “” (empty string), “O” or “X” alternatively each time we click on them. That is, the button initially displays nothing. When we click on it once, it displays “O”. When we click on it once more, it displays “X”. Finally when we click on it once more, it goes back to the empty string. Try it yourself!

  1. Answer:

import React, { Component } from 'react';

const style = {

*same as before*

}

const tiles = [" ", "X", "O"];

class Square extends Component {

  state = {

    currentBoard: 0

  }

  handleClick(){

    this.setState((prevState) => ({

      currentBoard: (prevState.currentBoard + 4) % 3

    }))

  }

  render() {

    return (

      <div style={style} onClick={this.handleClick.bind(this)}>

  {tiles[this.state.currentBoard]}

</div>

    )

  }

}

export default Square;

There is more than one way to achieve the same result, but here is how I did it: I first created an array tiles with the three strings we want to render:

const tiles = [" ", "X", "O"];

The idea is that in our state, we only need to store the current index of the string we are rendering.

Then, I created a state with the key “currentBoard” initialized to 0:

state = {

  currentBoard: 0

}

After that, I created a function handleClick that updates the state:

 handleClick(){

    this.setState((prevState) => ({

      currentBoard: (prevState.currentBoard + 4) % 3

    }))

 }

The (prevState.currentBoard + 4) % 3 part is a just a bit of arithmetic that makes sure our state goes from 0, 1, 2, then back to 0 again.

Finally, in the div element in our render function, I added an event handler onClick and passed in the function I just created:

<div style={style} onClick={this.handleClick.bind(this)}>

  {tiles[this.state.currentBoard]}

</div>

Remember we have to add .bind(this) for the this keyword to work!

Phew! We now have a semi-functional Tic Tac Toe you can play with your friends! Time for a coffee break!

Tic Tac Toe - Part 2

  1. After you’re back from your coffee break, you quickly realize that this game actually sucks with 2 reasons:

In order to determine if (1) a player has won or not and (2) whose player’s turn it is, we somehow need a state that knows everything. When you think about it, each Square right now has its own state, but none of the Squares are aware of each other. Therefore, there is no way for us to write a function to determine who won.

Here is where we learn about advanced state manipulation.

  1. Let’s first create a state in App.js:

state = {

  board: ["", "", "",

          "", "", "",

          "", "", ""],

  turn: "X"

}

“board” is an array of characters that represents our current board. “turn” stores whose turn it is.

  1. We now need to create a function updateBoard() that updates our board. This function is called whenever a player clicks on a square. It should take in an integer i that represents the index of the square being clicked, where i can be 0, 1, 2 … 8. For example, when 0 is being passed into the function, it means we need to update our square on the top left, and when 8 is being passed into the function, it means we need to update our square on the bottom right.

When we update our board, we need to do two things. First, we need to update our board, obviously. Second, we also need to update whose turn it is. Specifically, when “turn” is “X”, we need to change it to “O”, and vice versa. We can implement the function like so:

  updateBoard(i){

    const board = this.state.board.slice();

    const turn = this.state.turn;

    if(board[i]===""){

      board[i] = turn;

    }

    this.setState({

      board,

      turn: turn === "O" ? "X" : "O"

    })

  }

Notice a few things. First, we deep copied our board array with this.state.board.slice().

Instead of directly changing this.state.board, the official React tutorial recommends deep copying arrays and objects, changing them, then assigning them to the state with setState(). This improves React’s performance. This document explains why.

Detour - What is Deep Copying?

Deep copying is a common computer science terminology. Its opposite is shallow copying. To understand the concept, you first have to learn more about variable assignment. When we assign an object to a variable like so:

var x = [1,2,3];

We are setting x to point to the memory location of [1,2,3]. When we shallow copy the array, we are simply assigning the same memory location to another variable:

var y = x;

Therefore, both x and y point to the same copy of [1,2,3]. And when we modify the array through y like so:

y[0] = 999;

x will appear to also change:

console.log(x) // outputs [999,2,3]

This is because, again, x and y are referring to the same array. So if we change the array through y, it doesn’t matter if we read the array through x or y, the array is still changed.

On the other hand, if we deep copy the array with the slice() function:

var y = x.slice();

We are creating an identical, but separate, copy of [1,2,3]. Therefore, this time, if we modify y, x will remain unchanged.

Second, I used a ternary operator turn === "O" ? "X" : "O" in setState(). This is equivalent to something like:

If (turn===”O”) {

  “X”

} else {

  “O”

}

The ternary operator is a convenient shorthand that will make our code cleaner.

  1. With our updateBoard() function ready, let’s think about when we want it to be called?

Just as before, we want it to be called when a Square component is clicked. More specifically, we want to pass updateBoard() to Square.js, then give it to the <div>’s onClick as an event handler.

Turns out we can also pass functions to children as props. So we can do something like:

<Square handleClick={this.updateBoard.bind(this)} />

Then in Square.js, we can access the function via this.props.handleClick. How cool is that!

But wait a minute, remember updateBoard (which is accessible as this.props.handleClick) takes in a parameter i. How would Square know what number to pass to it?

  1. In this case, how about we pass in the parameter BEFORE we pass it to Square? Yes, we can do that! bind() allows us to do so! In addition to this, we can pass in other parameters to bind() like so:

<Square handleClick={this.updateBoard.bind(this, 0)}/>

In this case, when this.props.handleClick is called, it will (1) have its this set to the correct value and (2) have 0 passed in as i! So we’re passing parameters to updateBoard() BEFORE it is called, and all Square.js needs to do is to call updateBoard() when necessary. Problem solved!

  1. Now, let’s also pass in another props (we’ll call it “value”), so that Square knows what string to render like so:

<Square value={this.state.board[0]} handleClick={this.updateBoard.bind(this, 0)}/>

So our render function looks something like:

render() {

  const board = this.state.board;

  return (

    <div>

      <div>

        <Square value={board[0]} handleClick={this.updateBoard.bind(this, 0)}/>

        <Square value={board[1]} handleClick={this.updateBoard.bind(this, 1)}/>

        <Square value={board[2]} handleClick={this.updateBoard.bind(this, 2)}/>

      </div>

      <div>

        <Square value={board[3]} handleClick={this.updateBoard.bind(this, 3)}/>

        <Square value={board[4]} handleClick={this.updateBoard.bind(this, 4)}/>

        <Square value={board[5]} handleClick={this.updateBoard.bind(this, 5)}/>

      </div>

      <div>

        <Square value={board[6]} handleClick={this.updateBoard.bind(this, 6)}/>

        <Square value={board[7]} handleClick={this.updateBoard.bind(this, 7)}/>

        <Square value={board[8]} handleClick={this.updateBoard.bind(this, 8)}/>

      </div>

    </div>

  )

}

We are now passing in different “value” and “handleClick” props for each Square, so the Square components won’t need to know anything about the state!

  1. Now in our Square.js, let’s change our render function a bit in accordance to the props being passed in like so:

render() {

  return (

    <div style={style} onClick={this.props.handleClick}>{this.props.value}</div>

  )

}

  1. You can now delete our original handleClick function because we don’t need it anymore. Your App.js should look like:

import React, { Component } from 'react';

import Square from './Square';

import "./App.css";

class App extends Component {

  state = {

    board: ["", "", "",

            "", "", "",

            "", "", ""],

    turn: "X"

  }

 

  updateBoard(i){

    const board = this.state.board.slice();

    const turn = this.state.turn;

    if(board[i]===""){

      board[i] = turn;

    }

    this.setState({

      board,

      turn: turn === "O" ? "X" : "O"

    })

  }

  render() {

    const board = this.state.board;

    return (

      <div>

        <div>

          <Square value={board[0]} handleClick={this.updateBoard.bind(this, 0)}/>

          <Square value={board[1]} handleClick={this.updateBoard.bind(this, 1)}/>

          <Square value={board[2]} handleClick={this.updateBoard.bind(this, 2)}/>

        </div>

        <div>

          <Square value={board[3]} handleClick={this.updateBoard.bind(this, 3)}/>

          <Square value={board[4]} handleClick={this.updateBoard.bind(this, 4)}/>

          <Square value={board[5]} handleClick={this.updateBoard.bind(this, 5)}/>

        </div>

        <div>

          <Square value={board[6]} handleClick={this.updateBoard.bind(this, 6)}/>

          <Square value={board[7]} handleClick={this.updateBoard.bind(this, 7)}/>

          <Square value={board[8]} handleClick={this.updateBoard.bind(this, 8)}/>

        </div>

      </div>

    )

  }

}

export default App;

  1. Your Square.js should look like:

import React, { Component } from 'react';

const style = {

  backgroundColor: "white",

  border: "black solid 2px",

  textAlign: "center",

  fontSize: "20px",

  width: "40px",

  height: "40px",

  margin: -1,

  display: "inline-block",

  verticalAlign: "top",

}

class Square extends Component {

  render() {

    return (

      <div style={style} onClick={this.props.handleClick}>{this.props.value}</div>

    )

  }

}

export default Square;

Phewwwww! Now it’s time for another coffee break!

Tic Tac Toe - Part 3

  1. After you’re back from your second coffee break, you realize you forgot to deal with winning in the game. But you also realize by having a central “board” state, writing a function to determine if someone has won or not is actually super easy! All that hard work paid off!

Let’s go to App.js and write a function like so:

  playerWon() {

    const board = this.state.board;

    const winningConditions = [

      [0, 1, 2],

      [3, 4, 5],

      [6, 7, 8],

      [0, 3, 6],

      [1, 4, 7],

      [2, 5, 8],

      [0, 4, 8],

      [2, 4, 6],

    ];

    // determining if someone won

    for(let i = 0; i < winningConditions.length; i++){

      const [a, b, c] = winningConditions[i];

      if(board[a] !== "" &&

         board[a] === board[b] &&

         board[b] === board[c])

      {

        return board[winningConditions[i][0]]

      }

    }

    // check if board is full

    let isBoardFull = true;

    for(let i = 0; i < board.length; i++){

      if( board[i] === "" ){

        isBoardFull = false;

      }

    }

    if(!isBoardFull){

      // if no one has won and the board is not full, game continues

      return ""

    } else {

      // if no one has won when the board is full, it is a tie

      return "tie";

    }

  }

The function has nothing to do with React. It is just a simple algorithm. Read through the code and see if you understand everything!

In case you don’t understand everything, here’s a brief explanation:

We first hardcoded all winning conditions and store it into a 2d array in winningConditions. Then in the for loop, we looped through everything and check if somebody has won. If not, we have to check it there’s a tie or not. To do this, we first created a boolean isBoardFull to represent if the board is already full. If no one has won and the board is full, then it’s a tie. If no one has won but the board is not full, then the game continues.

  1. In our render function, let’s display the game message like so:

<h1>{this.playerWon()}</h1>

Phewwwwwwwwwwwwwwww! This time we’re finally done! You now have a fully functional Tic Tac Toe game and you should be proud of yourself!

Summary

In this section, we learnt how a child component can modify the state of a parent component. We did so by passing a function from the parent to the child. We extracted the state of each Square, then stored them in one place as “board” in App. By doing so, we were able to write a function to determine who is the winner of the game!

In the next section, we will look into creating React forms. See you soon!

Final Code

Here is the code for this section.