2016-12-09 4 views
3

Я делаю примитивное приложение для викторины с тремя вопросами до сих пор, все верно или ложно. В моем методе handleContinue есть вызов, чтобы направить пользователей, входящих в форму радио, в массив userAnswers. Он отлично работает для первого запуска handleContinue, после чего он выдает ошибку: Uncaught TypeError: this.state.userAnswers.push is not a function(…)Функция ReactJS Array.push не работает в setState

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> 
    ) 
    } 
} 

ответ

2

Do not modify state directly! В общем, попробуйте avoid mutation.

мутация массив на месте. Таким образом, если вы push в массив внутри setState, вы мутируете исходное состояние, используя push. И так как push возвращает новую длину массива вместо фактического массива, вы устанавливаете this.state.userAnswers на числовое значение, и именно поэтому вы получаете Uncaught TypeError: this.state.userAnswers.push is not a function(…) во втором прогоне, потому что вы не можете сделать push номеру.

Вместо этого вы должны использовать Array.prototype.concat(). Он не мутирует исходный массив и возвращает новый массив с новыми конкатенированными элементами. Это то, что вы хотите сделать внутри setState. Ваш код должен выглядеть примерно так:

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

Array.push не возвращает новый массив. попробуйте использовать

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

это вернет новый userAnswers массив

Ссылки: array push и array concat

+1

Этот ответ решает проблему, но на самом деле не объясняет, что происходит. Я рекомендую взглянуть на мой ответ, который объясняет проблему более подробно. – p4sh4

3

Вы должны относиться объект состояния, как неизменное, однако вам необходимо заново создать массив так его, указывающей новый объект, установите новый элемент, затем сбросьте состояние.

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) 
    } 

Другой альтернативой выше решения является использование .concat(), поскольку его возвращает сам новый массив. Это эквивалентно созданию новой переменной, но это гораздо более короткий код.

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

Это прекрасно работает, но не кажется оптимальным объявлять новую переменную каждый раз, когда запускается 'handleContinue'. Хотя я мог ошибаться, мысли? –

+2

Вы правы, это вызывает потерю памяти, но лучше, чем потенциальные условия гонки, которые могут возникнуть, если состояние мутировано напрямую. –

+1

Это не очень хороший ответ. Хотя это технически работает, гораздо короче и яснее использовать 'concat' и [docs] (https://facebook.github.io/react/docs/optimizing-performance.html#the-power-of-not -mutating-data) рекомендуют это делать. – p4sh4