2015-03-11 4 views
5

Scenario:Reagire JS non this.props.data definiti in getInitialState per il componente figlio anche se è definito in di rendering (ed è definito nella genitore)

  1. componenti riutilizzando (<Row /> -><Cell />) in lo stesso involucro per nonni (<ShippingTable /> -><Grid />) per il riutilizzo del codice.
  2. Per uno, assegno un array di dati e loop per riutilizzare componente figlio (<Row /> -><Cell />).
  3. Per il secondo, è solo un oggetto (con le stesse proprietà degli oggetti dell'array) che assegno direttamente nel rendering (non è necessario il ciclo this.props.data.map come già un solo oggetto).

Problema:

  1. Per l'array, ogni opera come richiesto. this.props.data viene passato ai bambini, lo stato viene aggiornato attraverso vari eventi e tutto va bene.
  2. Tuttavia, per il singolo oggetto, tutto funziona fino a <Row />. Anche se this.props.data contiene valori validi ed è assegnato correttamente al componente figlio <Cell />, in getInitialState <Cell />, è inspiegabilmente indefinito (o impostato su qualsiasi valore iniziale impostato in <ShippingTable /> di getInitialState). anche se, oltre ad essere chiamato in un ciclo .map, è lo stesso codice e lo stesso che funziona nello stesso rendering per la mia matrice di dati.
  3. Inoltre, this.props.data in <Cell /> 's rendering è infatti presente e accurata, ma a causa del setState rimasta getInitialState, this.state.data è indefinito (o impostato su qualsiasi valori impostati in <ShippingTable />' s getInitialState).
  4. Doppio inoltre, se imposto un ri-rendering dell'interfaccia utente (ad esempio, modifica un'altra proprietà di stato nel nonno <ShippingTable />), tutto funziona come mi aspetterei in primo luogo.

E 'come se getInitialState in <Cell /> solo per il mio oggetto singole percentuali viene chiamato prima un successo popolazione AJAX di setState, o non viene chiamato di nuovo quando lo stato viene modificato dopo essere stato popolato con dati aggiornati dal server.

Ecco una ridotta (ma ancora lunga) versione del codice vero e proprio:

AGGIORNAMENTO - Risolto

mai dimenticare che con Reagire, inviare sempre oggetti di scena verso il basso, inviare eventi fino, e tenere sempre indicare il più in alto possibile. Sono state rimosse le responsabilità di monitoraggio dello stato <Cell /> e <Row /> spostandole di nuovo su ShippingTable (facendo riferimento agli oggetti di puntamento trasmessi dai genitori) in cui lo stato viene effettivamente tracciato (e deve essere sempre tracciato). Come per @rallrall di seguito, sono uscito di pista e stavo inspiegabilmente lavorando contro il framework. Tutto molto chiaro in retrospettiva (anche se il motivo per cui l'approccio errato ha funzionato per l'array, ma non l'oggetto, ha infangato le acque, specialmente quando si è passati a lavorare anche su un array).

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')); 

risposta

7

getInitialState deve restituire lo stato del componente iniziale, a prescindere dai puntelli.Se si desidera realmente impostare un valore prop nello stato, è necessario utilizzare il gancio componentWillMount. Vedi docs.

Anche se sembra che tu stia lavorando contro il quadro qui. Quando si desidera modificare un valore dell'elica, il componente principale deve reagire e fornire nuovi oggetti di scena al componente figlio.

+2

Bene, duh. Grazie. Incredibile quante cose diventano chiare di nuovo quando torni un giorno dopo. Puntare verso il basso, eventi in alto e gestire lo stato il più vicino possibile alla cima. Grazie per il "sembra che tu stia lavorando contro il quadro qui". Un cattivo campione di React mi ha spinto a pensare che fosse necessario un onChange di input per tracciare il proprio stato ... – Ted

Problemi correlati