可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm making a primitive quiz app with 3 questions so far, all true or false. In my handleContinue
method there is a call to push the users input from a radio form into the userAnswers
array. It works fine for the first run of handleContinue
, after that it throws an error: Uncaught TypeError: this.state.userAnswers.push is not a function(…)
import React from "react"
export default class Questions extends React.Component {
constructor(props) {
super(props)
this.state = {
questionNumber: 1,
userAnswers: [],
value: ''
}
this.handleContinue = this.handleContinue.bind(this)
this.handleChange = this.handleChange.bind(this)
}
//when Continue button is clicked
handleContinue() {
this.setState({
//this push function throws error on 2nd go round
userAnswers: this.state.userAnswers.push(this.state.value),
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)
}
handleChange(event) {
this.setState({
value: event.target.value
})
}
render() {
const questions = [
"Blargh?",
"blah blah blah?",
"how many dogs?"
]
return (
<div class="container-fluid text-center">
<h1>{questions[this.state.questionNumber - 1]}</h1>
<div class="radio">
<label class="radio-inline">
<input type="radio" class="form-control" name="trueFalse" value="true"
onChange={this.handleChange}/>True
</label><br/><br/>
<label class="radio-inline">
<input type="radio" class="form-control" name="trueFalse" value="false"
onChange={this.handleChange}/>False
</label>
<hr/>
<button type="button" class="btn btn-primary"
onClick={this.handleContinue}>Continue</button>
</div>
</div>
)
}
}
回答1:
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
}
回答2:
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
回答3:
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
}
回答4:
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]
}));
回答5:
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.