Course: Section

Get Started with Modern React: Learn by Doing

Episode: Title

S03・V10: Lifting State Up

Date Created: July 17th, 2019
Last Updated: 27 days ago

Objective
  • We want the Board to control the value of all 9 Squares, which we will achieve by lifting state up.
Watch Video
Duration: 4m 34s

Lifting State Up

We already have the basic building blocks for our tic-tac-toe game. To have a complete game, we now need to alternate placing Xs and Os on the board. And we need a way to determine a winner.

Currently, each Square component maintains the game’s state. To check for a winner, we’ll maintain the value of each of the 9 Squares in one location. We can accomplish this by storing the game’s state in the parent Board component instead of in each Square. The Board component can then tell each Square what to display by passing a prop, just like we did when we passed an index number to each Square.

To collect data from multiple children, or to have two child components communicate with each other, you need to declare the shared state in their parent component instead. The parent component can pass the state back down to the children by using props. This keeps the child components in sync with each other and with the parent component. Lifting state into a parent component is common when React components are refactored.

The parent Board component should keep a record of the state of all 9 Squares as an array of elements which are either X, or O, or null. We will call this state array variable squares.

const Board = () => {
  // `squares` is a 9-element array; each element is `X`, `O`, or `null`.
  const renderSquare = i => {
    return (
      <Square value={i} />
    );
  };

We will use the useState hook to manage the state of the squares array.

const Board = () => {
  // `squares` is a 9-element array; each element is `X`, `O`, or `null`.  const [squares, setSquares] = useState(initialSquares);

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

We now need to define what the initialSquares state should be. The initialSquares should be an array of 9 null values, corresponding to the starting state of a tic-tac-toe game.

const Board = () => {
  const initialSquares = [    null, null, null,    null, null, null,    null, null, null,  ];  // `squares` is a 9-element array; each element is `X`, `O`, or `null`.
  const [squares, setSquares] = useState(initialSquares);

  const renderSquare = i => {
    return (
      <Square value={squares[i]} />
    );
  };

Currently, each square remembers its own state value, and the value is set by a click event. Now, the state of each square is remembered in the Board’s squares array, and the value of each element in the array can be passed as props from the Board to the Square. There is no longer a need for each Square to remember its own value.

We will start by removing the state variable value from the Square. The Square will now get its value from props, so let’s put props as the Square function component’s input argument. And now we can get the value from props. We no longer have the setValue function, so we will remove it from the onClick handler. We will temporarily have the handler do nothing.

const Square = props => {  return (
    <button
      className="square"
      onClick={() => {}    >
      {props.value}    </button>
  );
};

Inside our Board component, we will need to pass the squares[i] value down to the Square, not just the i as it does currently. Each Square will now receive a value prop that will be either an X, or an O. Or, it will be null for empty squares.

src/index.js
import React, { useState } from 'react';
import ReactDOM from 'react-dom';

import './index.css';

const Square = props => {
  return (
    <button
      className="square"
      onClick={() => {}}
    >
      {props.value}
    </button>
  );
};

const Board = () => {
  const initialSquares = [
    null, null, null,
    null, null, null,
    null, null, null,
  ];
  // `squares` is a 9-element array; each element is `X`, `O`, or `null`.
  const [squares, setSquares] = useState(initialSquares);

  const renderSquare = i => {
    return (
      <Square value={squares[i]} />    );
  };

  return (
    <div style={{
      backgroundColor: 'skyblue',
      margin: 40,
      padding: 20,
    }}>
      Board
      <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>
    </div>
  );
};

const Game = () => {
  return (
    <div className="game">
      Game
      <Board />
    </div>
  );
};

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

We can demonstrate this by passing some non-null values in the initialSquares array. View the effect in the browser. We will now undo the changes to the initialSquares array.

Summary

We explained “lifting up state” to a parent component when it is necessary, and demonstrated lifting up state from the Square to the Board component.

Next Up…

In the next video, we will look at how to make the Board component aware of when a child Square has been clicked.