简体   繁体   中英

Warning: setState(…): Can only update a mounted or mounting component. This usually means

I am working on an exercise in the Udemy Advanced Webdeveloper Bootcamp. The exercise asked to come up with a page of 32 boxes that randomly change colour (every x seconds). My solution is not exactly that. I change the color of all 32 boxes at the same time. It almost works. I get random 32 boxes initially, but does not change the color later. My console tells me I am doing something wrong with the setState. But I cannot figure out what. I think my changeColor is a pure function:

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

class Box extends Component {
    render() {

        var divStyle = {
            backgroundColor: this.props.color
        }

        return(
            <div className="box" style={divStyle}></div>
        );
    }
}

class BoxRow extends Component {
    render() {
        const numOfBoxesInRow = 8;
        const boxes = [];

        for(var i=0; i < numOfBoxesInRow; i++) {
            boxes.push(<Box color={this.props.colors[i]} key={i+1}/>);
        }

        return(
            <div className="boxesWrapper">
            {boxes}
            </div>
            );
    }
}

class BoxTable extends Component {

    constructor(props) {
        super(props);
        this.getRandom = this.getRandom.bind(this);
        this.changeColors = this.changeColors.bind(this);
        this.state = {
            randomColors: this.getRandom(this.props.allColors, 32) // hardcoding
        };

        this.changeColors();
    }

    changeColors() {
        setInterval(
            this.setState({randomColors: this.getRandom(this.props.allColors, 32)}), 5000);
    }

    getRandom(arr, n) {
        var result = new Array(n),
            len = arr.length,
            taken = new Array(len);
        if (n > len)
            throw new RangeError("getRandom: more elements taken than available");
        while (n--) {
            var x = Math.floor(Math.random() * len);
            result[n] = arr[x in taken ? taken[x] : x];
            taken[x] = --len in taken ? taken[len] : len;
        }
        return result;
    }

    render () {
        const numOfRows = 4;
        const rows = [];

        for(let i=0; i < numOfRows; i++) {
          rows.push(
              <BoxRow colors={this.state.randomColors.slice(8*i,8*(1+i))} key={i+1}/>
            )
        }

        return (
          <div className="rowsWrapper">
          {rows}
          </div>
        );
    }
}

BoxTable.defaultProps = {
    allColors: ["AliceBlue","AntiqueWhite","Aqua","Aquamarine","Azure","Beige",
    "Bisque","Black","BlanchedAlmond","Blue","BlueViolet","Brown","BurlyWood",
    "CadetBlue","Chartreuse","Chocolate","Coral","CornflowerBlue","Cornsilk",
    "Crimson","Cyan","DarkBlue","DarkCyan","DarkGoldenRod","DarkGray","DarkGrey",
    "DarkGreen","DarkKhaki","DarkMagenta","DarkOliveGreen","Darkorange",
    "DarkOrchid","DarkRed","DarkSalmon","DarkSeaGreen","DarkSlateBlue",
    "DarkSlateGray","DarkSlateGrey","DarkTurquoise","DarkViolet","DeepPink",
    "DeepSkyBlue","DimGray","DimGrey","DodgerBlue","FireBrick","FloralWhite",
    "ForestGreen","Fuchsia","Gainsboro","GhostWhite","Gold","GoldenRod","Gray",
    "Grey","Green","GreenYellow","HoneyDew","HotPink","IndianRed","Indigo",
    "Ivory","Khaki","Lavender","LavenderBlush","LawnGreen","LemonChiffon",
    "LightBlue","LightCoral","LightCyan","LightGoldenRodYellow","LightGray",
    "LightGrey","LightGreen","LightPink","LightSalmon","LightSeaGreen",
    "LightSkyBlue","LightSlateGray","LightSlateGrey","LightSteelBlue",
    "LightYellow","Lime","LimeGreen","Linen","Magenta","Maroon",
    "MediumAquaMarine","MediumBlue","MediumOrchid","MediumPurple",
    "MediumSeaGreen","MediumSlateBlue","MediumSpringGreen","MediumTurquoise",
    "MediumVioletRed","MidnightBlue","MintCream","MistyRose","Moccasin",
    "NavajoWhite","Navy","OldLace","Olive","OliveDrab","Orange","OrangeRed",
    "Orchid","PaleGoldenRod","PaleGreen","PaleTurquoise","PaleVioletRed",
    "PapayaWhip","PeachPuff","Peru","Pink","Plum","PowderBlue","Purple",
    "Red","RosyBrown","RoyalBlue","SaddleBrown","Salmon","SandyBrown",
    "SeaGreen","SeaShell","Sienna","Silver","SkyBlue","SlateBlue","SlateGray",
    "SlateGrey","Snow","SpringGreen","SteelBlue","Tan","Teal","Thistle",
    "Tomato","Turquoise","Violet","Wheat","White","WhiteSmoke","Yellow","YellowGreen"]
}

export default BoxTable

You need to use a lambda function in order to use setState inside setInterval

setInterval(() => {
        this.setState({randomColors: this.getRandom(this.props.allColors, 
                       32)});
                  }, 5000)

try to change your changeColors function to like this:

changeColors() {
        setInterval(() => this.setState({randomColors: this.getRandom(this.props.allColors, 32)}), 5000);
    }

the first param of setInterval is function, in your original code you already executed this setState and didn't passed the function itself

You will need to update the state after the component creation phase, inside componentDidMount()

class BoxTable extends Component {

    constructor(props) {
        super(props);
        this.getRandom = this.getRandom.bind(this);
        this.changeColors = this.changeColors.bind(this);
        this.state = {
            randomColors: this.getRandom(this.props.allColors, 32) // hardcoding
        };

        // delete this line 
        //this.changeColors();
    }
    // replace changeColors by componentDidMount, 
    // this function will be called automatically by react
    componentDidMount() {
        setInterval(
            this.setState({randomColors: this.getRandom(this.props.allColors, 32)}), 5000);
    }

    getRandom(arr, n) {
        var result = new Array(n),
            len = arr.length,
            taken = new Array(len);
        if (n > len)
            throw new RangeError("getRandom: more elements taken than available");
        while (n--) {
            var x = Math.floor(Math.random() * len);
            result[n] = arr[x in taken ? taken[x] : x];
            taken[x] = --len in taken ? taken[len] : len;
        }
        return result;
    }

    render () {
        const numOfRows = 4;
        const rows = [];

        for(let i=0; i < numOfRows; i++) {
          rows.push(
              <BoxRow colors={this.state.randomColors.slice(8*i,8*(1+i))} key={i+1}/>
            )
        }

        return (
          <div className="rowsWrapper">
          {rows}
          </div>
        );
    }
}

You are calling this.setState before the component has mounted (from the constructor). Try instead making your first this.ChangeColors from the componentDidMount lifecycle function.

Additionally, it's not a bad idea to clear the interval when it unmounts in componentWillUnMount

Edit: By changing the interval to call after the first wait you do prevent the error, for now . I'd recommend using the lifecycle functions to build good habits. It's just a temporary assignment, but in a full project you'd be putting yourself at risk to break the component again later by making it possible to call this.setState before it is reliably mounted.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM