React JS this.props.data未在子组件的getInitialState中定义,即使它是在render中定义的(并在父级中定义)

时间:2015-03-11 20:06:31

标签: javascript reactjs

情景:

  1. 在同一个祖父母包装器(<Row /> - &gt; <Cell />)中重复使用组件(<ShippingTable /> - &gt; <Grid />)以重复使用代码。
  2. 首先,我分配一个数据数组并循环以重用子组件(<Row /> - &gt; <Cell />)。
  3. 对于第二个,它只是一个对象(具有与数组对象相同的属性),我只是直接在渲染中分配(不需要this.props.data.map循环已经只是一个对象)。
  4. 问题:

    1. 对于阵列,所有都按要求工作。 this.props.data传递给孩子,状态通过各种事件更新,一切都很顺利。
    2. 但是,对于单个对象,一切正常,直到<Row />。即使this.props.data包含有效值并且已正确分配给子<Cell />组件,但在<Cell />的getInitialState中,它仍然是莫名其妙地未定义(或设置为设置的任何初始值) <ShippingTable />&#39; s getInitialState)。即使除了在.map循环中调用之外,它与完全相同的代码和数据在我的数据数组的相同渲染中工作
    3. 此外,this.props.data渲染中的<Cell />确实存在且准确,但由于getInitialState中的setState失败,this.state.data未定义(或设置为任何值在<ShippingTable />&#39; s getInitialState)中设置。
    4. 此外,如果我强制重新呈现UI(例如,更改祖父母<ShippingTable />中的另一个州属性),那么一切都会如我所期望的那样起作用。
    5. 好像getInitialState中的<Cell />只是我的单个百分比对象在成功的setAX的AJAX数据集之前被调用,或者它没有被再次调用在从服务器填充更新数据后更改状态。

      这是实际代码的精简版(但仍然很长):

      更新 - 已解决

      永远不要忘记,使用React,总是发送道具,发送事件,并始终保持状态尽可能远。删除了<Cell /><Row />的状态跟踪职责,将其移回到ShippingTable(而不是引用父级传递的道具),其中实际跟踪状态(并且应始终跟踪) 。根据下面的@rallrall,我偏离了轨道并且莫名其妙地反对该框架。回想起来都非常清楚(尽管为什么不正确的方法对阵列起作用而不是对象确实困扰了水域 - 特别是当将对象切换到阵列时也是如此)。

         var ShippingInput = React.createClass({
              getInitialState: function() {
                  return { value: this.props.value };
              },
              handleChange: function(event) {
                  var value = event.target.value;
                  ...conversion to decimal and validation
                  this.props.onChange(value);
                  this.setState({ value: value });
              },
              render: function() {
                  return (
                      <Input type="text" placeholder="0.00" bsStyle={this.validationState()} addonBefore={addOnBefore}
                        value={this.state.value} label={this.props.label} ref="input" onChange={this.handleChange} 
                        groupClassName={this.props.groupClassName} labelClassName={this.props.labelClassName} onKeyDown={this.props.onKeyDown} />
              );
            }
          });
      
          var Cell = React.createClass({
              propTypes: {
                  data: React.PropTypes.number.isRequired,
                  onChange: React.PropTypes.func.isRequired,
                  onRowEdit: React.PropTypes.func.isRequired
              },
              getInitialState: function() {
                  // At this point, this.props.data is undefined *only for <Cell data={this.props.percentages} /> in <Row /> even though that prop is not null there.
                  return {value: this.props.data};
              },
              handleChange: function(value) {
                  this.props.onChange(value);
                  this.setState({ value: value });
              },
              render: function() {
                  var edit = this.props.edit;
                  var showInput = edit ? 'group-class' : 'group-class hide'; 
                  var showText = edit ? 'hide' : 'btn btn-default btn-sm';
      
                  var val = this.props.isRates ? accounting.formatMoney(this.state.value) : this.state.value;
      
                  // {this.state.value} is undefined here for only the percentages object
                  // {this.props.data} is *not undefined* 
                  var input = <ShippingInput type="text" label={this.props.label} value={this.state.value} ref="input"
                        isRates={this.props.isRates} groupClassName={showInput} labelClassName="label-class sr-only" onKeyDown={this.handleKeyDown} onChange={this.handleChange} />;
      
                  var text = (<a href="#" className={showText} onClick={this.handleClick}>{val}</a>);
      
                  return ( <td>{input}{text}</td> );
              }
          });
      
          var Row = React.createClass({
              propTypes: {
                  data: React.PropTypes.object.isRequired,
                  onCellChange: React.PropTypes.func.isRequired,
                  onRowCommit: React.PropTypes.func.isRequired
              },
              getInitialState: function() {
                  return {edit: false};
              },
              handleChange: function(prop, val) {
                  this.props.onCellChange(prop, val);
              },
              ...
              render: function() {
                  var edit = this.state.edit;
                  var text = edit ? 'fa fa-save fa-fw' : 'fa fa-edit fa-fw';
                  return <tr>
                      <Cell data={this.props.data.Canada} isRates={this.props.isRates}  label="Canada" edit={edit} onRowEdit={this.handleRowEdit} onRowCommit={this.handleRowCommit}  onChange={this.handleChange.bind(null, "Canada")} />
                      <Cell data={this.props.data.Us} isRates={this.props.isRates}  label="United States" edit={edit} onRowEdit={this.handleRowEdit}  onRowCommit={this.handleRowCommit}  onChange={this.handleChange.bind(null, "Us")} />
                      <Cell data={this.props.data.International} isRates={this.props.isRates} label="International" edit={edit} onRowEdit={this.handleRowEdit}  onRowCommit={this.handleRowCommit} onChange={this.handleChange.bind(null, "International")} />
                      <td>
                          <Button href="#" ref="commit" onClick={this.handleRowCommit} bsStyle="primary" bsSize="small"><span className={text}></span></Button>
                      </td>
                  </tr>;
              }
          });
      
          var Grid = React.createClass({
              propTypes: {
                  data: React.PropTypes.array.isRequired,
                  percentages: React.PropTypes.object.isRequired,
                  onCellChange: React.PropTypes.func.isRequired,
                  onRowCommit: React.PropTypes.func.isRequired
              },
              render: function() {
                  var rows = this.props.data.map(function(rowData, index) {
                      var id = rowData["Id"];
                      return <Row key={id} isRates={true} data={rowData} onCellChange={this.props.onCellChange.bind(null, index)} onRowCommit={this.props.onRowCommit.bind(null, index)} onRowDelete={this.props.onRowDelete.bind(null, index)} />;
                  }, this);
      
                  return (
                      <Table striped bordered hover responsive>
                        <thead>
                        <tr>
                          <th className="col-sm-4">Order Subtotal (up to)</th>
                          <th className="col-sm-2">Canada</th>
                          <th className="col-sm-2">US</th>
                          <th className="col-sm-2">International</th>
                          <th className="col-sm-1"></th>
                        </tr>
                        </thead>
                          <tbody>
                              {rows}
                              <tr><td colSpan="5">If the order subtotal is greater than the largest amount on the above chart, the following rates apply:</td></tr>
                              <Row key="Percentages" isRates={false} data={this.props.percentages} onCellChange={this.props.onPercentCellChange} onRowCommit={this.props.onPercentRowCommit} />
                          </tbody>
                      </Table>  
      
                  );
              }
          });
      
          var ShippingTable = React.createClass({
              getInitialState: function() {
                  return this.props.initialData;
              },
              loadFromServer: function() {
                  $.getJSON(this.props.url, function(data) {
                      if (!data || data.Success === false) {
                          toastr.error('Error loading shipping costs. Please refresh the page and try again.');
                      } else if (this.isMounted()) {
                          // This change is not reflected in Row/Cell for this.state/props.percentages until after force a UI update (via handleAdd and handleCancel 
                          // where this.state.add changes) even though a) percentages (a single object) holds valid values here and does all the way down to <Row />
                          // and b) there is no similar issue with this.state.data.
                          this.setState({ data: data.Value.ShippingCostMatrix, percentages: data.Value.ShippingPercentage });
                      }
                  }.bind(this));
              },
              componentDidMount: function() {
                  this.loadFromServer();
              },
              handleCellChange: function(rowIdx, prop, val) {
                  var row = copy(this.state.data[rowIdx]);
                  row[prop] = val;
                  var rows = this.state.data.slice();
                  rows[rowIdx] = row;
                  rows.sort(sortBySubtotal);
                  this.setState({data: rows});
              },
              handlePercentCellChange: function(prop, val) {
                  var row = copy(this.state.percentages);
                  row[prop] = val;
                  this.setState({percentages: row});
              },
              handleAdd: function(event) {
                  event.preventDefault();
                  this.setState({ add: true});
              },
              handleAddCancel: function(event) {
                  event.preventDefault();
                  this.setState({ add: false});
              },
              render: function() {
                  var ctrl;
                  if (this.state.add) {
                      ctrl = (<NewRow onAddCancel={this.handleAddCancel} onRowAdd={this.handleRowAdd} />);
                  }
                  else {
                      ctrl = (
                          <div>
                          <p><a onClick={this.handleAdd} className="btn btn-primary btn-lg">Add</a></p>
                          <Grid data={this.state.data} percentages={this.state.percentages}
                               onCellChange={this.handleCellChange} onPercentCellChange={this.handlePercentCellChange} onRowCommit={this.handleRowCommit}  onPercentRowCommit={this.handlePercentRowCommit} onRowDelete={this.handleRowDelete} />
                          </div>
                       );
                  }
      
                  return <div>{ctrl}</div>;
              }
          });
      
          //React.render(<ShippingTable initialData={ {data : [], percentages: { Canada: 1, Us: 1.25, International: 2.25, Id: 1 }, add: false} } 
          React.render(<ShippingTable initialData={ {data : [], percentages : {}, add: false} } 
              url="/admin/shipping/costs" update="/admin/shipping/update" create="/admin/shipping/create" delete="/admin/shipping/delete" updatePercentage="/admin/shipping/updatepercentage" />, document.getElementById('shipTable'));
      

1 个答案:

答案 0 :(得分:7)

getInitialState应该返回初始组件状态,而不管道具。如果您确实想在状态中设置prop值,则应使用componentWillMount挂钩。见docs

虽然看起来你在这里反对这个框架。当您想要更改prop值时,父组件应对此做出反应,并为子组件提供新的道具。