reactjs.org Tutorial

Before you read this page you need to read Linking JSX File ES6 first.
There is an excellent tutorial on the reactjs.org/tutorial website to help you get started using React.
This sample creates three React components (Square, Board and Game)
Square - This component renders a single <button>
Board - This component renders 9 Squares to create the grid
Game - This component renders the Board plus some interactive components
The sample displayed here is slightly different to the one on the reactjs website because this one uses TypeScript interfaces.
Lets start by creating the following two files (index.html and index.tsx)


index.htm

Create this file and embed the necessary CSS.

<! DOCTYPE html> 
<html>
<head>
<style>

body {
  font: 14px "Century Gothic", Futura, sans-serif;
  margin: 20px;
}
ol, ul {
  padding-left: 30px;
}
.board-row:after {
  clear: both;
  content: "";
  display: table;
}
.status {
  margin-bottom: 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;
}
.kbd-navigation .square:focus {
  background: #ddd;
}
.game {
  display: flex;
  flex-direction: row;
}
.game-info {
  margin-left: 20px;
}

</style>
</head>

<body>
<div id="app"></div>
</body>
</html>

index.tsx

Add the necessary interfaces for Props and State.

import * as React from 'react';    
import * as ReactDOM from 'react-dom';

interface IProps_Square {
}
interface IState_Square {
}
class Square extends React.Component <IProps_Square , IState_Square> {
  render() {
    return (
      <button className="square">
      </button>
    );
  }
}

interface IProps_Board {
}
interface IState_Board {
}
class Board extends React.Component <IProps_Board, IState_Board> {

  renderSquare(i) {
    return <Square />;
  }

  render() {
    const status = 'Next player: X';

    return (
      <div>
        <div className="status">{status}</div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    );
  }
}

interface IProps_Game {
}
interface IState_Game {
}
class Game extends React.Component <IProps_Game, IState_Game> {
  render() {
    return (
      <div className="game">
        <div className="game-board">
          <Board />
        </div>
        <div className="game-info">
          <div></div>
          <ol></ol>
        </div>
      </div>
    );
  }
}

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

Run the code and check that you are seeing the following in the web page
SS


Changes 1 - Passing Data

Let's pass some data from our Board component to our Square component.
Find the IProps_Square interface and add a property called "value" that has a string data type.

interface IProps_Square { 
   value : string
}

Find the Square's render method and display the property "value" on the button.

render() { 
  return (
    <button className="square">
      { this.prop.value }
    </button>

Find the Board's renderSquare method.
Pass in a property called "value" to the Square assigning it the value of i.

renderSquare(i) { 
  return < Square value={i} />;
}

Run the code and check that you are seeing the following in the web page.
SS


Changes 2 - Interactive Component

Let's fill the Square component with an "X" when we click it.
Find the Square's render method and lets add an onClick event.

render() { 
  return (
    <button
        className="square"
        onClick={ () => alert('click') }
     >
      { this.prop.value }
    </button>
  )
}

Run the code and check that you see a prompt when you click on any square on the board.
Find the IState_Square interface and add a state called "sButtonText" that has a string data type.

interface IState_Square { 
   value : string
}

Add a constructor to the Square class above the render method.
Initialise the "value" state inside this constructor.

constructor(props) { 
  super(props)
  this.state = {
     value : null
  }
}

Add an "onClick" event inside the Square's render method to set the state "value" to 'X'.
Change the Square's render method to display the state "value".

render() { 
  return (
    <button
       className = "square"
       onClick={ () => this.setState( { value : 'X' } ) }
    >
      { this.state.value }
    </button>
  )
}

Run the code and check.


Changes 3 - Lifting to Parent

The value of each of the 9 squares needs to be stored at the Board level which is the parent component.
Find the IState_Board interface and add a state called "sSquaresArray".

interface IState_Board { 
   squares : Array < string >
}

Add a constructor to the Board class above the renderSquare method.
Initialise the array inside this constructor.

constructor(props) { 
  super(props);
  this.state = {
     squares : Array(9).fill(null)
  }
}

Change the Board's renderSquare method to pass in the value from the array instead.

renderSquare(i) { 
  return <Square value = { this.state.squares[i] } />
}

Run the code and check.


Changes 4 - Update Board State

When the Square is clicked we need to update the Board component (or state).
We cannot update the Board's state directly from Square.
Let's pass a function into Square that can be called when the Square is clicked.
Find the IProps_Square interface and add a property called "OnClick".

interface IProps_Square { 
   value : string,
   OnClick : any
}

Add an "handleClick" method to the Board class above the render method.
The slice method copies the array.

handleClick(i) { 
  const squares2 = this.state.squares.slice();
  squares2[i] = 'X';
  this.setState( { squares : squares2 } );
}

Change the Board's renderSquare method to pass in a function.
Notice we have added parenthesis to prevent JavaScript from inserting a semicolon.

renderSquare(i) { 
  return (
    <Square
      value = { this.state.squares[i] }
      OnClick = { () => this.handleClick(i) }
    />
  )
}

Change the button onClick event inside the Square's render method to call the property "OnClick".
Change the Square's render method to display the property "pButtonText".

render() { 
  return (
    <button
       className = "square"
       onClick = { () => this.props.OnClick() }
    >
      { this.props.value }
    </button>
  )
}

Delete the Square's constructor method.
Delete the state called value from the IState_Square interface.
Run the code and check.


Changes 5 - Taking Turns

When the squares are clicked the value needs to switch between 'X' and 'O'.
Find the IState_Board interface and add a state called "sEnterX" that has a boolean data type.

interface IState_Board { 
   squares : Array < string >,
   xIsNext : boolean
}

Initialise the state "xIsNext" to true inside the Board's constructor .

constructor(props) { 
  super(props);
  this.state = {
     squares : Array(9).fill(null),
     xIsNext : true
  }
}

Change the Board's "handleClick" method to toggle the value of the state "xIsNext".

handleClick(i) { 
  const squares2 = this.state.squares.slice();
  squares2[i] = this.state.xIsNext ? 'X' : 'O';
  this.setState( {
       squares : squares2
       xIsNext : !this.state.xIsNext
   } );
}

Change the "status" constant inside the Board's render method to indicate which player has the next turn.

render() { 
  const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}

Run the code and check.


Changes 6 - Finding the Winner

Let's identify when there is a winner and which player it is.
Add the following function at the end of the file below the Game class.
This will check for a winner and return 'X', 'O' or null as appropriate.

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

Change the "status" constant inside the Board's render method.

render() { 
  const winner = calculateWinner(this.state.sSquaresArray);
  let status
   if (winner) {
    status = 'Winner : ' + winner;
  } else {
    status = 'Next player : ' + (this.state.xIsNext ? 'X' : 'O');
  }
}

Change the Board's "handleClick" method to return early if someone has won the game or if the square has already been clicked.

handleClick(i) { 
  const squares_slice

  if (calculateWinner(squares_slice) ) { 
    return;
  }
  if (squares_slice[i]) {
    return;
  }

Run the code and check.


Changes 7 - History of Moves


Changes 8 - Showing Past Moves


Final Code




© 2019 Better Solutions Limited. All Rights Reserved. © 2019 Better Solutions Limited TopPrevNext