CSSTransitionGroup(React)抱怨找不到密钥

时间:2015-03-01 21:59:59

标签: javascript reactjs

我正在尝试使用CSSTransitionGroup为SentenceList中的Sentences制作动画。当按下“下一个”按钮时,我希望下一个句子动画,列表中的第一个按钮淡出。但是我收到此错误消息:

  

数组中的每个子节点都应该有一个唯一的“键”支柱。检查   句子的渲染方法。

我不明白为什么会这样,因为当我将Sentence推入我的列表时,我将{sentence.id}作为“关键”道具传递给我。 React不应该知道每个句子键在渲染时都是这样定义的吗?

我已经尝试在Sentence渲染方法中再次定义键但无济于事。我的状态变化是否使React失去对当前句子键的追踪?

感谢您的帮助!

SentenceList:

var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;


var SentenceList = React.createClass({
  getInitialState: function() {
    return {
      sentences: this.props.sentences
    }
  },
  //receives sentence and new blip from Sentence
  addBlip: function(sentence, value) {
    //see where in the loaded sentences we are
    var i = this.state.sentences.indexOf(sentence),
        sentences = this.state.sentences,
        // callback within a callback (post), the context changes inside the callback so we need to set this to self 
        self = this; 

    $.post(
      '/sentences/' + sentence.id + '/blips',
       {blip: {body: value}},
       //set sentence we blipped into as answered
       //reset state to reload sentences state after post
       function(response) {
         sentences[i].answered = true;
         // sentences[i].statistics = response.statistics;
         // put dummy content first then work it out in the backend to receive the format you want to receive (better to work from front to back)
         sentences[i].statistics = [
          {word: "butts", frequency: "95%"},
          {word: "dogs", frequency: "2%"},
          {word: "vegetables", frequency: "1%"},
          {word: "sun", frequency: "2%"}
         ];
         self.setState({sentences: sentences});
       });
  },

  //take in a sentence (sent from Sentence) and find current position in loaded sentences and set it to dismissed, then reload list
  dismissSentence: function(sentence) {
    var i = this.state.sentences.indexOf(sentence),
        sentences = this.state.sentences;

        sentences[i].dismissed = true;
        this.setState({sentences: sentences});
  },

  //list undismissed sentences and take out the first 3 for display
  topThreeRemainingSentences: function() {
    var unanswered = _.where(this.state.sentences, {dismissed: false}); 
    return unanswered.slice(0, 3);
  },

  render: function() {
    var remaining = this.topThreeRemainingSentences(),
        sentences = [],
        index = 0;

    //loop through sentences until we have 3 remaining sentences loaded    
    while (index <= (remaining.length - 1)) {
      var sentence = remaining[index];
      sentences.push(
        <Sentence key={sentence.id}
                  isActive={index == 0}
                  isNext={index == 1}
                  isNnext={index == 2}
                  onDismiss={this.dismissSentence}
                  onSubmitBlip={this.addBlip}
                  details={sentence} />
      )
      index = index + 1;
    }


    return (
      <ReactCSSTransitionGroup transitionName="animate">
      <div>{sentences}</div>
      </ReactCSSTransitionGroup>

    )
  }
});

句:

var Sentence = React.createClass({
  getDefaultProps: function() {
    return {
      onSubmitBlip: function() { console.log(arguments) }
    }
  },
  //pass sentence and new blip to submit function
  addBlip: function(e) {
    e.preventDefault();
    var blipBody = this.refs.newBlip.getDOMNode().value
    this.props.onSubmitBlip(this.props.details, blipBody);
  },

  //send sentence to List to set it to dismissed
  dismissSentence: function(e) {
    e.preventDefault();
    this.props.onDismiss(this.props.details);
  },

  render: function() {  
    var phrase = this.props.details.body,
        phrase_display = phrase.split("*"),
        before = phrase_display[0],
        after = phrase_display[1],
        positionClass,
        stats;

    if (this.props.isActive) {
      positionClass = "active-sentence"
    } else if (this.props.isNext) {
      positionClass = "next-sentence"
    } else if (this.props.isNnext) {
      positionClass = "nnext-sentence"
    }

    //find stats for sentence if answered from json and push them into array ["word", x%]
    if (this.props.details.answered) {
      var words = [];
      this.props.details.statistics.forEach(function(statistic) {
        words.push(<li className="stats-list"><span className="stats-list-word">{statistic.word} </span>
          <span className="stats-list-percent">{statistic.frequency} </span> </li>)
      })

      stats = <div><span className="stats-list-header">others said:</span> {words}</div>
    }

    if (this.props.isActive) {
      nextButton = <div className="next-button" onClick={this.dismissSentence}>V</div>

    }
    if (this.props.isNext) {
      nextButton = <div></div>
    }
    if (this.props.isNnext) {
      nextButton = <div></div>
    }



    return (
      <div className={"blipForm " + positionClass}>
        {before}

        <form onSubmit={this.addBlip}>
          <input type="text"
                 ref="newBlip" />
        </form>

        {after}
        {nextButton}
        <br/>
        <ul>{stats}</ul>
      </div>
      )
  }
});      

1 个答案:

答案 0 :(得分:0)

在Sentence组件的<li>方法中创建的render元素需要key属性:

this.props.details.statistics.forEach(function(statistic) {
  words.push(<li className="stats-list" key={statistic.id}>...</li>);
});