score:19

Accepted answer

Do not modify state directly! In general, try to avoid mutation.

Array.prototype.push() mutates the array in-place. So essentially, when you push to an array inside setState, you mutate the original state by using push. And since push returns the new array length instead of the actual array, you're setting this.state.userAnswers to a numerical value, and this is why you're getting Uncaught TypeError: this.state.userAnswers.push is not a function(…) on the second run, because you can't push to a number.

You need to use Array.prototype.concat() instead. It doesn't mutate the original array, and returns a new array with the new concatenated elements. This is what you want to do inside setState. Your code should look something like this:

this.setState({
  userAnswers: this.state.userAnswers.concat(this.state.value),
  questionNumber: this.state.questionNumber + 1
}

score:0

The correct way to mutate your state when you want to push something to it is to do the following. Let's say we have defined a state as such:

const [state, setState] = useState([])

Now we want to push the following object into the state array. We use the concat method to achieve this operation as such:

let object = {a: '1', b:'2', c:'3'}

Now to push this object into the state array, you do the following:

setState(state => state.concat(object))

You will see that your state is populated with the object. The reason why concat works but push doesn't is because of the following

Array.prototype.push() adds an element into original array and returns an integer which is its new array length.



Array.prototype.concat() returns a new array with concatenated element without even touching in original array. It's a shallow copy.

score:3

I have found a solution. This shoud work for splice and others too. Lets say that I have a state which is an array of cars:

this.state = {
  cars: ['BMW','AUDI','mercedes']
};
this.addState = this.addState.bind(this);

Now, addState is the methnod that i will use to add new items to my array. This should look like this:

  addState(){

let arr = this.state.cars;
arr.push('skoda');
this.setState({cars: arr});
}

I have found this solution thanks to duwalanise. All I had to do was to return the new array in order to push new items. I was facing this kind of issue for a lot of time. I will try more functions to see if it really works for all functions that normally won't. If anyone have a better idea how to achieve this with a cleaner code, please feel free to reply to my post.

score:3

The recommended approach in later React versions is to use an updater function when modifying states to prevent race conditions:

this.setState(prevState => ({
  userAnswers: [...prevState.userAnswers, this.state.value] 
}));

score:6

Array.push does not returns the new array. try using

this.state.userAnswers.concat([this.state.value])

this will return new userAnswers array

References: array push and array concat

score:6

You should treat the state object as immutable, however you need to re-create the array so its pointing to a new object, set the new item, then reset the state.

 handleContinue() {
    var newState = this.state.userAnswers.slice();
    newState.push(this.state.value);

    this.setState({
      //this push function throws error on 2nd go round
      userAnswers: newState,
      questionNumber: this.state.questionNumber + 1
    //callback function for synchronicity
    }, () => {
      if (this.state.questionNumber > 3) {
        this.props.changeHeader(this.state.userAnswers.toString())
        this.props.unMount()
      } else {
        this.props.changeHeader("Question " + this.state.questionNumber)
      }
    })
    console.log(this.state.userAnswers)
  }

Another alternative to the above solution is to use .concat(), since its returns a new array itself. Its equivalent to creating a new variable but is a much shorter code.

this.setState({
  userAnswers: this.state.userAnswers.concat(this.state.value),
  questionNumber: this.state.questionNumber + 1
}

Related Query

More Query from same tag