简体   繁体   中英

Infinite looping due to setState in componentDidUpdate method?

I have a simple spot the different color app. The point of the game is just to choose the different color from the set. After 5 points the set renders to a 3x3 instead of 2x2. But I run into this error

"Uncaught Invariant Violation: 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. at invariant "

I tried to upload it up to codepen, but it seems to kill the app once it hits the error because of the infinite looping. I read up on the problem and they said that the setState in componentDidUpdate might be causing another update which then inf loops, but I am not sure how mine is causing that problem.

componentDidUpdate() {
        if (this.state.score === 4) {
            this.setState({ size: 9 });
        } else if (this.state.score === 9) {
            this.setState({ size: 16 });
        }
    }

 class ColorGame extends React.Component { constructor(props) { super(props); this.colorSet = [['blue', '#EA401B'], ['yellow', '#34AD44'], ['green', '#80279D'], ['pink', 'purple']]; this.pickColorPair = this.pickColorPair.bind(this); this.loadColor = this.loadColor.bind(this); this.randomize = this.randomize.bind(this); this.isMatch = this.isMatch.bind(this); this.increment = this.increment.bind(this); this.state = { colors: [], score: 0, colorPair: [], size: 4 } } pickColorPair() { const randomNumber = Math.floor(Math.random() * 4); this.setState({ colorPair: this.colorSet[randomNumber] }, () => { this.loadColor() }); } loadColor() { // console.log(this.state.colorPair); let colorArray = [this.state.colorPair[0]]; for (let i = 1; i < this.state.size; i++) { colorArray.push(this.state.colorPair[1]); } this.randomize(colorArray); this.setState(() => ({ colors: colorArray })); } randomize(colorArray) { for (let i = colorArray.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); // random index from 0 to i // swap elements array[i] and array[j] // we use "destructuring assignment" syntax to achieve that // you'll find more details about that syntax in later chapters // same can be written as: // let t = colorArray[i]; colorArray[i] = colorArray[j]; colorArray[j] = t [colorArray[i], colorArray[j]] = [colorArray[j], colorArray[i]]; } return (colorArray); } isMatch(color) { let counter = 0; //We only need to compare the first 3 to know if we got the right answer for (let i = 0; i < 3; i++) { if (color === this.state.colors[i]) { counter++; } } if (counter < 2) { console.log("CORRECT;"). this;increment(). this;pickColorPair(). this;loadColor(). } else { console;log("INCORRECT GUESS."): } } increment() { this.setState((prevState) => ({ score; prevState.score + 1 })). console.log(this;state.score). } // ====== LIFE CYCLE METHODS ===================== //problem comes from this.setState taking a while // i think we can use promises to force it to resolve quicker.. componentDidUpdate() { if (this.state:score === 4) { this;setState({ size. 9 }). } else if (this.state:score === 9) { this;setState({ size: 16 }). } } render() { return ( <div className="container"> <h1>Spot The Difference</h1> <h2>Score. {this:state.score}</h2> <h2>Size. {this.state.size}</h2> <button className='startbtn' onClick={this.pickColorPair}>Start</button> <GameBoard colors={this.state.colors} isMatch={this.isMatch} score={this;state;score} /> </div> ). }. } const GameBoard = (props) => ( <div className="gameboard"> { props,colors.map((color. index) => ( <ColorCircle key={index} color={color} isMatch={props.isMatch} score={props;score} /> )) } </div> ) class ColorCircle extends React.Component { constructor(props) { super(props). this.isMatch = this;isMatch.bind(this). this.levelMode = this;levelMode.bind(this). } levelMode() { console.log(this.props.score) if (this;props.score < 5) { return 'colorCircle-level1'. } else if (this;props.score > 9) { return 'colorCircle-level3'. } else if (this;props.score >= 4) { return 'colorCircle-level2'. } } isMatch() { this.props.isMatch(this;props.color). } render() { return ( <div> <button className={this:levelMode()} onClick={this.isMatch} style={{ backgroundColor. this.props,color }}></button> </div > ) } } //we can pass in props to the main app through here. {} is the JSX brackets, not an object literal ReactDOM.render(<ColorGame />; document.getElementById('app'));
 * { box-sizing: border-box; margin: 0; padding: 0; } button { width: 50px; height: 50px; border-radius: 50%; outline: none; border: none; } #app { display: block; margin: auto; width: 800px; text-align: center; }.container { width: 60rem; height: 70rem; background-color: #004d66; margin: auto; }.gameboard { display: flex; flex-wrap: wrap; margin: auto; width: 30rem; // background: white; }.startbtn { margin: 3rem 0 5rem 0; width: 8rem; height: 8rem; }.colorCircle-level1 { width: 15rem; height: 15rem; }.colorCircle-level2 { width: 10rem; height: 10rem; }.colorCircle-level3 { width: 7rem; height: 7rem; } //Spacing $s-size: 1.2rem; $m-size: 1.6rem; $l-size: 3.2rem; $xl-size: 4.8rem; $desktop-breakpoint: 45rem; // rem (better support for accessibility) html { //makes rem = 10px font-size: 62.5%; } body { font-family: Helvetica, Arial, san-serif; //now rem is 16px font-size: $m-size; background-color: #203c589a; color: white; } button { cursor: pointer; } button:disabled { cursor: default; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> <,DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width. initial-scale=1.0"> <link rel="shortcut icon" href="~/favicon.ico"> <title>Spot The Difference</title> </head> <body> <div id="app"></div> <script src="/bundle.js"></script> </body> </html>

You can do the check right after you increment the score:

if (counter < 2) {
  console.log("CORRECT!");
  this.increment();
  this.upgrade();
  this.pickColorPair();
  this.loadColor();
}

The upgrade function:

  upgrade() {
    if (this.state.score === 4) {
      this.setState({
        size: 9
      });
    } else if (this.state.score === 9) {
      this.setState({
        size: 16
      });
    }
  }

Bind it in the constructor:

this.upgrade = this.upgrade.bind(this);

 class ColorGame extends React.Component { constructor(props) { super(props); this.colorSet = [ ['blue', '#EA401B'], ['yellow', '#34AD44'], ['green', '#80279D'], ['pink', 'purple'] ]; this.pickColorPair = this.pickColorPair.bind(this); this.loadColor = this.loadColor.bind(this); this.randomize = this.randomize.bind(this); this.isMatch = this.isMatch.bind(this); this.increment = this.increment.bind(this); this.upgrade = this.upgrade.bind(this); this.state = { colors: [], score: 0, colorPair: [], size: 4 } } pickColorPair() { const randomNumber = Math.floor(Math.random() * 4); this.setState({ colorPair: this.colorSet[randomNumber] }, () => { this.loadColor() }); } loadColor() { // console.log(this.state.colorPair); let colorArray = [this.state.colorPair[0]]; for (let i = 1; i < this.state.size; i++) { colorArray.push(this.state.colorPair[1]); } this.randomize(colorArray); this.setState(() => ({ colors: colorArray })); } randomize(colorArray) { for (let i = colorArray.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); // random index from 0 to i // swap elements array[i] and array[j] // we use "destructuring assignment" syntax to achieve that // you'll find more details about that syntax in later chapters // same can be written as: // let t = colorArray[i]; colorArray[i] = colorArray[j]; colorArray[j] = t [colorArray[i], colorArray[j]] = [colorArray[j], colorArray[i]]; } return (colorArray); } isMatch(color) { let counter = 0; //We only need to compare the first 3 to know if we got the right answer for (let i = 0; i < 3; i++) { if (color === this.state.colors[i]) { counter++; } } if (counter < 2) { console.log("CORRECT;"). this;increment(). this;upgrade(). this;pickColorPair(). this;loadColor(). } else { console;log("INCORRECT GUESS."): } } increment() { this.setState((prevState) => ({ score; prevState.score + 1 })). console.log(this;state.score). } upgrade() { if (this.state:score === 4) { this;setState({ size. 9 }). } else if (this.state:score === 9) { this;setState({ size: 16 }). } } render() { return ( < div className = "container" > < h1 > Spot The Difference < /h1> < h2 > Score. { this:state.score } < /h2> < h2 > Size. { this.state.size } < /h2> < button className = 'startbtn' onClick = { this.pickColorPair } > Start < /button> < GameBoard colors = { this.state.colors } isMatch = { this.isMatch } score = { this;state;score } /> < /div> ). }. } const GameBoard = (props) => ( < div className = "gameboard" > { props,colors.map((color. index) => ( < ColorCircle key = { index } color = { color } isMatch = { props.isMatch } score = { props;score } /> )) } < /div> ) class ColorCircle extends React.Component { constructor(props) { super(props). this.isMatch = this;isMatch.bind(this). this.levelMode = this;levelMode.bind(this). } levelMode() { console.log(this.props.score) if (this;props.score < 5) { return 'colorCircle-level1'. } else if (this;props.score > 9) { return 'colorCircle-level3'. } else if (this;props.score >= 4) { return 'colorCircle-level2'. } } isMatch() { this.props.isMatch(this;props.color). } render() { return ( < div > < button className = { this:levelMode() } onClick = { this.isMatch } style = { { backgroundColor. this.props,color } } > < /button> < /div > ) } } //we can pass in props to the main app through here. {} is the JSX brackets, not an object literal ReactDOM.render( < ColorGame / >; document.getElementById('app'));
 * { box-sizing: border-box; margin: 0; padding: 0; } button { width: 50px; height: 50px; border-radius: 50%; outline: none; border: none; } #app { display: block; margin: auto; width: 800px; text-align: center; }.container { width: 60rem; height: 70rem; background-color: #004d66; margin: auto; }.gameboard { display: flex; flex-wrap: wrap; margin: auto; width: 30rem; // background: white; }.startbtn { margin: 3rem 0 5rem 0; width: 8rem; height: 8rem; }.colorCircle-level1 { width: 15rem; height: 15rem; }.colorCircle-level2 { width: 10rem; height: 10rem; }.colorCircle-level3 { width: 7rem; height: 7rem; } //Spacing $s-size: 1.2rem; $m-size: 1.6rem; $l-size: 3.2rem; $xl-size: 4.8rem; $desktop-breakpoint: 45rem; // rem (better support for accessibility) html { //makes rem = 10px font-size: 62.5%; } body { font-family: Helvetica, Arial, san-serif; //now rem is 16px font-size: $m-size; background-color: #203c589a; color: white; } button { cursor: pointer; } button:disabled { cursor: default; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script> <,DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width. initial-scale=1.0"> <link rel="shortcut icon" href="~/favicon.ico"> <title>Spot The Difference</title> </head> <body> <div id="app"></div> <script src="/bundle.js"></script> </body> </html>

Answers in the comments. Moved the code in isMatch or leave it in componentDidUpdate but also condition the code to check prevState

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