Search code examples
javascriptreactjsstatesetstate

this.setState isn't working (my state is a number)


I am having a problem with: this.setState({score: counter}); For some reason it's not updating. Why? Even when I do this.setState({score: 5}) or score:"a string" it still doesn't work. Am I missing something? Whatever I do, the state won't change. I have to add extra description because the website won't let me post my question.

displayResultsHandler = () => {
    var counter = 0;
    for(let i = 0; i < this.state.correctAnswers.length; i++) {
      if(this.state.correctAnswers[i] === this.state.userAnswers[i]) {
        counter = counter+1;
      }
    }

this.setState({
  score: counter
});

console.log("counter: " + counter);
console.log("score: " + this.state.score); // For some reason, this is always zero

}

I added my full code below. Maybe it will help.

import React, { Component } from 'react';
import './App.css';
import Title from './Title/Title';
import Question from './Question/Question';
import Aux from './hoc/Aux';
import SubmitButton from './SubmitButton/SubmitButton';

class App extends Component {
  state = {
    questionArray: [
      "What is 9+10",
      "How many goals did Ronaldo score against Spain in the World Cup 2018",
      "Who Stole Ronaldo's (CR7) greates ever goal?",
      "Which one of these players ruined the NBA",
      "Who is currently number 1 in the internet L rankings?"
  ],
    answerChoicesArray: [
      ["1", "19", "21", "90", "-1"],
      ["1", "3", "5", "0", "-1"],
      ["Pepe", "Messi", "Casillas", "Benzema", "Nani"],
      ["Allen Iverson", "Kevin Durant", "Steph Curry", "Lebron James", "Russel Westbrook"],
      ["Drake", "Pusha T", "Russel Westbrook", "Lil Xan", "Russ"]
    ],

    correctAnswers: ["21", "3", "Nani", "Kevin Durant", "Russ"],

    userAnswers: ["", "", "", "", ""],

    score: 0

  }

  updateUserAnswersHandler = (oldArray, index, value) => {
    const newArray = oldArray;
    newArray[index] = value;
    this.setState({
      userAnswers: newArray
    });
  }

  displayResultsHandler = () => {
    var counter = 0;
    for(let i = 0; i < this.state.correctAnswers.length; i++) {
      if(this.state.correctAnswers[i] === this.state.userAnswers[i]) {
        counter = counter+1;
      }
    }

    this.setState({
      score: counter
    });

    console.log("counter: " + counter);
    console.log("score: " + this.state.score); // For some reason, this is always zero

    if(this.state.score < 5) {
      alert("You're dumb asf. Please leave");
    } else {
      alert("Welcome to NASA");
    }
  }

  render() {
    // var userAnswers;
    // console.log(userAnswers); How do I pass this as a prop? this.userAnswers? nope. Therefore it has to be a state
    console.log("User Answers are: " + this.state.userAnswers);
    return (
      <div className="App">
        <div className="container">
          
            <Title/>
            <h2>Only the most genius of individuals will pass</h2>
            <hr/>
            <Question
              correctAnswers={this.state.correctAnswers}
              updateUserAnswersHandler={this.updateUserAnswersHandler}
              userAnswers={this.state.userAnswers}
              questionArray={this.state.questionArray}
              answerChoicesArray={this.state.answerChoicesArray} />
            <SubmitButton
              clicked = {this.displayResultsHandler} />
        </div>
      </div>
    );
  }
}

export default App;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>


Solution

  • State updates are asynchronous. Your call to setState just queues the update, it doesn't make it happen immediately.

    You're also using the wrong overload of setState (you're not alone, lots of people do!). When you're setting new state based on existing state, you cannot use the version that just accepts an object. You must use the callback version. If you want to use the resulting state when you're done, you can pass a second function which will be called when the state update is complete:

    displayResultsHandler = () => {
        this.setState(
            // Updater
            prevState => {
                var counter = 0;
                for(let i = 0; i < prevState.correctAnswers.length; i++) {
                  if(prevState.correctAnswers[i] === prevState.userAnswers[i]) {
                    counter = counter+1;
                  }
                }
                return {score: counter};
            },
            // Completion callback
            () => {
                console.log("score: " + this.state.score);
            }
        );
    };
    

    However, the documentation says this about using the completion callback:

    Generally we recommend using componentDidUpdate() for such logic instead.

    This is all covered on this page of the documentation, and in the setState API docs.


    Just as a side note, a couple of advanced JavaScript features (destructuring and shorthand property notation) and a prosaic one (increment operator) can make that code a bit more concise:

    displayResultsHandler = () => {
        this.setState(
            // Updater
            ({correctAnswers, userAnswers}) => {                  // *** Destructuring
                let score = 0;
                for (let i = 0; i < correctAnswers.length; i++) {
                  if (correctAnswers[i] === userAnswers[i]) {
                    ++score;                                      // *** Increment
                  }
                }
                return {score};                                   // *** Shorthand property
            },
            // Completion callback
            () => {
                console.log("score: " + this.state.score);
            }
        );
    };
    

    Some people might even use reduce instead of that for loop; I'm not convinced readability isn't impacted, though:

    displayResultsHandler = () => {
        this.setState(
            // Updater
            ({correctAnswers, userAnswers}) => ({
                score: correctAnswers.reduce(
                    (score, answer, index) =>
                        score + (answer === userAnswers[index] ? 1 : 0),
                    0
                )
            }),
            // Completion callback
            () => {
                console.log("score: " + this.state.score);
            }
        );
    };
    

    (You could even drop the conditional operator and just use score + (answer === userAnswers[index]) since true coerces to 1 and false coerces to 0, but...)