Gagak
Gagak

Reputation: 161

Timer class based, Warning: Cannot update during an existing state transition (such as within `render`)

I have tried to read similar solutions for this problem and none of it able to solve my problem. From the console error, it stated that I have an error in this line

this.setState({ submitted: true, finalScore });

I am currently making a quiz app using ReactJS where it has a timer. Which, once the timer reaches to 0 - It will automatically submit the answer.

this is my render

  render() {
    const { questionBank, submitted, finalScore, minute, second } = this.state;


    return (
      <Box>
        <h2>Pop Quiz!</h2>
        <h3>Time remaining: {minute}:{second< 10 ? `0${ second }` : second}</h3>
        { minute === 0 && second === 0
            ? this.handleSubmit() : <div>{!submitted &&
          questionBank.length > 0 &&
          questionBank.map(
            ({ question, answers, correct, questionId }, index) => (
              <QuestionBox
                key={questionId}
                index={index + 1}
                question={question}
                options={answers}
                selected={(answer) =>
                  this.computeAnswer(answer, correct, questionId)
                }
              />
            )
          )}
              {!submitted && (
                  <Box align="end">
                    <Button
                        primary
                        label="Submit"
                        onClick={() => this.handleSubmit()}
                    />
                  </Box>
              )}

              {submitted && <Result score={finalScore} playAgain={this.playAgain} />}

        </div>
        }
      </Box>
    );
  }

this is my timer

timer = () => {
    this.myInterval = setInterval(() => {
      const { second, minute } = this.state

      if (second > 0) {
        this.setState(({ second }) => ({
          second: second - 1
        }))
      }
      if (second === 0) {
        if (minute === 0) {
          clearInterval(this.myInterval)
        } else {
          this.setState(({ minute }) => ({
            minute: minute - 1,
            second: 59
          }))
        }
      }
    }, 1000)
  }

componentDidMount() {
    this.getQuestions();
    this.timer();

  }

and this is handleSubmit

handleSubmit() {
    const {
      correctQuestionAnswered,
      questionBank: { length },
    } = this.state;

    const { db } = this.props;

    const finalScore = ((correctQuestionAnswered / length) * 100).toFixed(2);
    const currentdate = new Date();
    const dateTime =
      currentdate.getDate() +
      "/" +
      (currentdate.getMonth() + 1) +
      "/" +
      currentdate.getFullYear() +
      " @ " +
      currentdate.getHours() +
      ":" +
      currentdate.getMinutes() +
      ":" +
      currentdate.getSeconds();

    db.put({
      _id: new Date().toJSON(),
      submit_datetime: dateTime,
      score: finalScore,
    });

    this.setState({ submitted: true, finalScore }); // <--- This is the line that I get an error
    this.forceUpdate();
  }

Where did I go wrong that gives me this result? I am still fairly new in reactJS

Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state

//Edit: After tweaking some more, I managed somehow fixed it by moving the submitHandler to the timer when it reaches 00:00 and erase the this.forceUpdate() within submitHandler. However, I am still not entirely sure why the previous design doesn't work.

Upvotes: 1

Views: 57

Answers (2)

Lu7ky
Lu7ky

Reputation: 61

I didn't go over your code in great detail since you fixed the issue, but I believe it is because you called your state inside the render() method. Each time there is a change (for you always, since you are counting down) your state is stuck in an infinite loop. it should be mentioned in the constructor() or use a tool like redux.

edit: beat to the punch and much more elegantly...

Upvotes: 0

aryanm
aryanm

Reputation: 1259

Simple, you are calling setState in your render method, which is not allowed in React.

This line of code in your render method is illegally causing the setState to be called:

{ minute === 0 && second === 0
        ? this.handleSubmit()

You should move this state check into a componentDidUpdate() method in your component's class. In short, componentDidUpdate() is called whenever the props or state of your component changes (in other words, whenever your component renders). Learn about componentDidUpdate() here

Here you should check the minute and second state, and call the handleSubmit() method accordingly:

componentDidUpdate() {
    if(this.state.minute === 0 && this.state.second === 0) {
        this.handleSubmit()
    }
}

And remove the minute === 0 && second === 0 ternary from the render method.

If you have any problems while implementing my solution, please let me know in a comment.

Upvotes: 2

Related Questions