This post is intended as a description of the application and it’s structure, not as a full tutorial on how to re-create it. All code is included except for styling.

This project extends my earlier post about the new React Command Line Interface and quickly generating a new React application. I’ll now walk you through the creation of my first application using React, a decidedly hideous tic-tac-toe application which can be seen here.

If you’re unfamiliar with React, it is a front end framework for the creation of UI. Essentially React allows a simple method of modularizing your application into its smallest possible components and then constructing your interface with those components.

Your application will end up with a tree-like structure where data is passed down through the use of “props” and the higher level data your application relies on is maintained in the “state” hopefully of only a few higher level components. So what does a Tic-Tac-Toe application look like?

At the top level your application needs two things. You need an entry point for the browser (index.html) and you need an entry point for your JavaScript application (index.js). Both are very simple and will probably be identical for most applications you might create using the React CLI.

index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` in this folder.
      To create a production bundle, use `npm run build`.
    -->
    <script src="./src/index.js"></script>
  </body>
</html>

There isn’t much to the index.html file, we have a single div to inject our dynamic content into and we reference the entry point of our Javascript application. Index.js is almost as simple, all this file does is load a single React Component “App.js” and inject that Component into the page inside of our #root div.

  index.js

  import React from 'react';
  import ReactDOM from 'react-dom';
  import App from './App';
  import './index.css';

  ReactDOM.render(
    <App />,
    document.getElementById('root')
  );

Here’s our App.js file, description of function below:

  App.js

  import React, { Component } from 'react';
  import './App.css';

  import Tile from "./tile.js";
  import ResetButton from "./ResetButton.js";
  import Announcement from "./Announcement.js";

  export default class App extends Component {
    constructor(){
      super();
      this.state = {
        gameBoard: [
          ' ',' ',' ',
          ' ',' ',' ',
          ' ',' ',' '
        ],
        winner: null,
        turn: 'x'
      }
    }

    resetBoard(){
      this.setState({
        gameBoard: [
          ' ',' ',' ',
          ' ',' ',' ',
          ' ',' ',' '
        ],
        winner: null,
        turn: 'x'
      });
    }

    updateBoard(loc, player) {
      //Game Over!
      console.log(this.state.winner, this.state.turn, this.state.gameBoard);
      if(this.state.winner !== null) {
        //make game over component visible
        console.log("Winner", this.state.winner);
        return;
      }
      if(this.state.gameBoard[loc]=== 'x' || this.state.gameBoard[loc]=== 'o'){
        //invalid move
        return;
      }
      let currentGameBoard = this.state.gameBoard;
      currentGameBoard.splice(loc, 1, this.state.turn);
      this.setState({gameBoard: currentGameBoard}, function(){
        //Check if there is a winner or draw
        var moves = this.state.gameBoard.join('').replace(/ /g,'');
        console.log('Moves:', moves, 'Winner:', this.state.winner);
        if(moves.length === 9) {
          this.setState({winner: 'd'});
          //Make game over component visible
          return;
        } else {
          var topRow = this.state.gameBoard[0] + this.state.gameBoard[1] + this.state.gameBoard[2];
          if(topRow.match(/xxx|ooo/)){
            this.setState({winner: this.state.turn});
            return;
            }
          var middleRow = this.state.gameBoard[3] + this.state.gameBoard[4] + this.state.gameBoard[5];
          if(middleRow.match(/xxx|ooo/)){
            this.setState({winner: this.state.turn});
            return;
          }
          var bottomRow = this.state.gameBoard[6] + this.state.gameBoard[7] + this.state.gameBoard[8];
          if(bottomRow.match(/xxx|ooo/)){
            this.setState({winner: this.state.turn});
            return;
          }
          var leftCol = this.state.gameBoard[0] + this.state.gameBoard[3] + this.state.gameBoard[6];
          if(leftCol.match(/xxx|ooo/)){
            this.setState({winner: this.state.turn});
            return;
          }
          var middleCol = this.state.gameBoard[1] + this.state.gameBoard[4] + this.state.gameBoard[7];
          if(middleCol.match(/xxx|ooo/)){
            this.setState({winner: this.state.turn});
            return;
          }
          var rightCol = this.state.gameBoard[2] + this.state.gameBoard[5] + this.state.gameBoard[8];
          if(rightCol.match(/xxx|ooo/)){
            this.setState({winner: this.state.turn});
            return;
          }
          var leftDiag = this.state.gameBoard[0] + this.state.gameBoard[4] + this.state.gameBoard[8];
          if(leftDiag.match(/xxx|ooo/)){
            this.setState({winner: this.state.turn});
            return;
          }
          var rightDiag = this.state.gameBoard[2] + this.state.gameBoard[4] + this.state.gameBoard[6];
          if(rightDiag.match(/xxx|ooo/)){
            this.setState({winner: this.state.turn});
            return;
          }
          this.setState({turn: (this.state.turn === 'x') ? 'o' : 'x' });
        }
      }, this);
    }

    render(){
      return (
        <div className="container">
          <div className="menu">
            <h1 className="pink">Ugly Tic Tac Toe</h1>
            <Announcement winner={this.state.winner} />
            <ResetButton reset={this.resetBoard.bind(this)}/>
          </div>
          {this.state.gameBoard.map(function(value, i){
            return (
              <Tile
                key={i}
                loc={i}
                value={value}
                updateBoard={this.updateBoard.bind(this)}
                turn={this.state.turn} />
            );
          }.bind(this))}
        </div>
      );
    }
  }

App.js is the meat and potatoes of our application. This component is also responsible for State in our Tic-Tac-Toe application. What exactly does that mean? Well, State represents the current state of our data at a given point in time. Since React watches your data and propagates that data down through the tree to components which utilize it, the updates can then be made automatically when the state changes.

App.js is a much larger file but it contains the business logic for our application as well as the state and much of it is decidedly repetitive. Generally, you should only store data in state that cannot be calculated on the fly. So for instance any user data that will be dynamically generated is a good candidate for a state variable.

In our Tic-Tac-Toe application we don’t need much data, we need an initial state for our game board, we need to know which player starts the game and we need to know if the game has been won. Technically I suppose you could calculate if there is a winner without using state but I didn’t realize that until after finishing the project and changing would have required a pretty extensive refactor.

The App component has 3 child components. Announcement, which displays an announcement when the game ends.

  Announcement.js

  import React, { Component } from 'react';

  export default class Announcement extends Component {

    render(){
      return(
        <div className={this.props.winner ? 'visible' : 'hidden'}>
          <h2>Game Over</h2>
        </div>
      )
    }
  }

ResetButton, which contains exactly what you’d expect.

  ResetButton.js

  import React from 'react';

  export default class ResetButton extends React.Component {

    render(){
      return (
        <button onClick={this.props.reset}>Reset</button>
      )
    }
  }

And Tile, which displays a tile for each position on the board.

  Tile.js

  import React from 'react';

  export default class Tile extends React.Component {
    tileClick(props) {
      props.updateBoard(props.loc, props.turn);
    }
    render() {
      return (
        <div className={"tile " + this.props.loc} onClick={() => this.tileClick(this.props)}>
          <p>{this.props.value}</p>
        </div>
      );
    }
  }

The most complex of the child components is the Tile, which must know it’s location on the board and be able to update the state of the game board when a player makes a move. OnClick handlers in React have a pretty interesting implementation and probably deserve their own post. But, to sum it up, when we need to run a function of a parent, such as our App Component when the user makes a play, we pass the function from the parent to the child as a prop and then use that function after detecting a click event.

We run an update function on the parent (App) to make sure the tile selected is empty, if it is we place the players move and then check to see if the game ended, either because of a draw or one player winning the game. If the game ended the message that the game is over is displayed. Play only continues at this point if the board is reset.

And that’s pretty much it, minus the very minimal styling you have a functional Tic-Tac-Toe application.

Check out my YouTube channel for more related content.