Search code examples
reactjsdomrenderingreact-dom

Tutorial: Intro to React - Rerendering the whole collection instead of the most recently changed element


I have been going through the official "Tutorial: Intro to React"

The complete code can be seen here

A board of nine squares is provided. The state of the squares is "lifted up" into the Game class to minimize the amount of stateful components. Clicking on a square will change the state in said Game class. What I observed is that now every square gets rerendered instead of just the one I clicked.

In order to grasp the problem I counted the number of calls to the return-function of the Square functional component.

The lines of code in question:

import React, {Component} from 'react'

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

class Board extends React.Component {
    renderSquare(i) {
        return (
            <Square
                value={this.props.squares[i]}
                onClick={() => this.props.onClick(i)}
            />
        );
    }

    render() {
        return (
            <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>
        );
    }
}

export default class Game extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            squares: Array(9).fill(null),
            xIsNext: true,
        };
    }

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

    render() {
        const squares = this.state.squares

        return (
            <div className="game">
                <div className="game-board">
                    <Board
                        squares={squares}
                        onClick={i => this.handleClick(i)}
                    />
                </div>
            </div>
        );
    }
}

index.js:

import React from 'react';
import ReactDOM from 'react-dom';
import Game from './Game';

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

I expected react would rerender only the square I clicked.

Instead all nine squares get rerendered after each click on any of the free squares.

In my understanding of react one of the beneficial features is the virtual DOM with it's ability to decide which nodes need to be updated on the "real" DOM. Isn't the whole purpose of 'state' to calculate which part of the screen has to be rerendered?

I realize that when I change the state of "Game" the whole "game component" will be rerendered. But isnt that exactly what should be avoided? How can I minimize the amount of stateful components and still have good performance? In this case its only 9 squares that rerender. But imagine I had a field of 200x200 squares...


Solution

  • Try this implementation of Square with shouldComponentUpdate

    class Square extends React.Component {
      shouldComponentUpdate(nextProps) {
        return this.props.value !== nextProps.value;
      }
    
      render() {
        const { value, onClick } = this.props;
    
        return (
          <button className="square" onClick={onClick}>
            {value}
          </button>
        );
      }
    }