dragonmnl
dragonmnl

Reputation: 15558

Why is my React's component click method triggered at rendering time?

I am rewriting ReactJS's "starter game" in ES6 with arrow functions (to remove the use of .bind(), class properties and typescript.

Apparently in my code handleSquareClick is triggered at rendering time, setState is hence executed, resulting in error

Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

Why?

Game.tsx:

import React, { PureComponent } from 'react';
import Board from "../Board";

class Game extends PureComponent {

    state = {

        history: [{
            squares: Array(9).fill(null)
        }],

        currentStep: 0,

        xIsNext: true
    };


    handleSquareClick = (i) => {
        console.log(i);

        // ...more code here

        this.setState({
           history: [...historyClone, { squares }],
           currentStep: historyClone.length,
           xIsNext: !xIsNext
        });

    };


    render() {
        const { history, currentStep } = this.state;

        const current = history[currentStep];

        return (
            <div>
                {  }
                <Board
                    squares={current.squares}
                    handleSquareClick={this.handleSquareClick}

            <
        );
    }

}

export default Game;

Board.tsx

import React, {PureComponent} from 'react';
import {
    Title,
    Square
} from './styles';

class Board extends PureComponent {

    getSquareStringValue(value) {
        if (value === 0) return 'X';
        else if (value === 1) return 'O';
        else return '';
    }

    renderSquare(props) {
        const { key, value } = props;

        return (
            <Square
                    key={key}
                    className="square"
                    onClick={this.props.handleSquareClick(value)}>
                { this.getSquareStringValue(value) }
            </Square>
        )
    }

    render() {
        const { title, squares } = this.props;

        return (
            <div className="board">
                <Title>{title}</Title>
                <div>
                    {[...Array(3).keys()]
                        .map((i) => (
                            <div className="board-row" key={i}>
                                {[...Array(3).keys()]
                                    .map((j) => this.renderSquare({
                                        key: (i+1)*(j+1),
                                        value: squares[i]
                                    }))
                                }
                            </div>
                        ))
                    }
                </div>
            </div>
        )
    }

    static defaultProps = {
        title: "Board",
    };
}



export default Board;

Board/styles.ts

import styled from 'styled-components';

export const Title = styled.h1`
    font-size: 1rem;
`;


export const Square = styled.div`
    height: 16px;
    width: 16px;
    border: 1px solid #000;
    display: inline-block;
`;

Upvotes: 1

Views: 56

Answers (1)

Ali Hayder
Ali Hayder

Reputation: 325

You just need to change this:

onClick={this.props.handleSquareClick(value)}>

to this:

onClick={()=> this.props.handleSquareClick(value)}>

because onClick expects a function as a value, but what you were doing is that you were calling this function.

We know that if we put () at the end of a function, that function is called. So what I changed is to pass the function without calling it, by using a () => ... arrow function.

Upvotes: 2

Related Questions