2014-09-11 24 views
12

Sto riscrivendo l'interfaccia utente per la mia app Web in react.js e sono un po 'stordito dal seguente problema.Creazione di elementi in React JS

Ho una pagina che visualizza i dati ottenuti tramite una richiesta AJAX e al di sotto viene visualizzato un modulo per inviare nuovi dati. Tutto bene.

Ora, voglio aggiungere un elemento <select> al modulo e recuperare i valori da una posizione diversa (url).

Il codice corrente (senza il <select>) si presenta così (semplificato un po ', ma tutti i particolari di lavoro sono gli stessi, ma segue in gran parte il tutorial sul sito react.js):

var tasks_link = $('#tasks_link'); 

var getDataMixin = { 
     loadDataFromServer: function() { 
      $.ajax({ 
       url: this.props.url, 
       dataType: 'json', 
       success: function(data) { 
        this.setState({data: data}); 
       }.bind(this), 
       error: function(xhr, status, err) { 
        console.error(this.props.url, status, err.toString()); 
       }.bind(this) 
      }); 
     }, 
     getInitialState: function() { 
      return {data: []}; 
     }, 
     componentDidMount: function() { 
      this.loadDataFromServer(); 
     } 
    }; 

var sendDataMixin = { 
     handleDataSubmit: function(senddata) { 
      $.ajax({ 
       url: this.props.url, 
       dataType: 'json', 
       contentType: 'application/json', 
       type: 'POST', 
       data: senddata, 
       success: function(data) { 
        var curr_d = this.state.data; 
        var curr_d_new = curr_d.concat([data]); 
        this.setState({data: curr_d_new}); 
       }.bind(this), 
       error: function(xhr, status, err) { 
        console.error(this.props.url, status, err.toString()); 
       }.bind(this) 
      }); 
     } 
    }; 

var taskForm = React.createClass({ 
     handleSubmit: function() { 
      var name = this.refs.task_name.getDOMNode().value.trim(); 
      if (!name) { 
       return false; 
      } 
      this.props.onTaskSubmit(JSON.stringify({name: name})); 
      this.refs.task_name.getDOMNode().value = ''; 
      return false; 
     }, 
     render: function() { 
      return (
       <form className="well base_well new_task_well" onSubmit={this.handleSubmit}> 
        <div className="form-group"> 
         <div className="input-group"> 
          <span className="input-group-addon no_radius">Task name</span> 
          <input type="text" className="form-control no_radius" id="add_new_project_input" ref="task_name"/> 
         </div> 
        </div> 
        <button type="button" className="btn btn-default no_radius add_button" id="add_new_task_btn" type="submit">Add task</button> 
       </form> 
      ); 
     } 
    }); 

var taskBox = React.createClass({ 
     mixins: [getDataMixin, sendDataMixin], 
     render: function() { 
      return (
       <div id="project_box" className="taskBox"> <taskList data={this.state.data} /> 
        <taskForm onTaskSubmit={this.handleDataSubmit}/> </div> 
      ); 
     } 
    }); 

tasks_link.click(function() { 
     React.renderComponent(
      <taskBox url="/api/tasks/" />, 
      document.getElementById('content_container') 
     ); 
    }); 

Ora , Posso aggiungere un elemento select aggiungendo uno getDataMixin a TaskForm, recuperando i dati e creando un elenco di opzioni possibili, ma avrò bisogno di moduli con molte liste e l'approccio non sembra in scala (a causa di conflitti di denominazione o avrò bisogno di usare qualcosa di diverso dai mixin).

Così ho pensato di creare un separato React class, che sarebbe solo avere la getDataMixin, ricevono l'url API tramite il genitore impostando il suo props, e rendono l'elemento <select>; e usa questa classe all'interno del modulo. Ma non ho idea di come accedere al valore selezionato (dal momento che un genitore non può accedere al suo figlio refs). Quindi ho bisogno di un altro modo per passare il valore selezionato "su".

Oppure, nel caso in cui ciò non sia possibile, una spinta nella direzione corretta - Non voglio finire con un sacco di codice non riutilizzabile (parte del motivo per cui sono passato a reagire era usare mescolare e mantenere il codice su un minimo ragionevole e leggibile).

risposta

17

Un genitore può accedere ai propri dati di riferimento del bambino fintanto che si imposta il figlio come riferimento all'interno del genitore, è possibile accedere a qualsiasi riferimento all'interno del figlio. Quindi ad esempio qualcosa come this.refs.childRef.refs.nestedChildRef. Tuttavia questa è una pessima idea a meno che tu non stia solo leggendo le informazioni (e gestendo errori come gli ref non esistenti correttamente dal momento che il genitore non sa se il riferimento del bambino è impostato correttamente).

Ma per fortuna c'è una soluzione molto semplice. Hai ragione, vorrai creare un componente figlio che ottenga i propri dati, piuttosto che un mixin. Tuttavia, il modo in cui gestisci il dare l'elemento selezionato al genitore è semplicemente passare una funzione onChange dal genitore come faresti con un in-built <select> in risposta.

Ecco un esempio di questa relazione genitore-figlio:

var MyParent = React.createClass({ 
    getInitialState: function() { 
     return { 
      childSelectValue: undefined 
     } 
    }, 
    changeHandler: function(e) { 
     this.setState({ 
      childSelectValue: e.target.value 
     }) 
    }, 
    render: function() { 
     return (
      <div> 
       <MySelect 
        url="http://foo.bar" 
        value={this.state.childSelectValue} 
        onChange={this.changeHandler} 
       /> 
      </div> 
     ) 
    } 
}); 

var MySelect = React.createClass({ 
    propTypes: { 
     url: React.PropTypes.string.isRequired 
    }, 
    getInitialState: function() { 
     return { 
      options: [] 
     } 
    }, 
    componentDidMount: function() { 
     // get your data 
     $.ajax({ 
      url: this.props.url, 
      success: this.successHandler 
     }) 
    }, 
    successHandler: function(data) { 
     // assuming data is an array of {name: "foo", value: "bar"} 
     for (var i = 0; i < data.length; i++) { 
      var option = data[i]; 
      this.state.options.push(
       <option key={i} value={option.value}>{option.name}</option> 
      ); 
     } 
     this.forceUpdate(); 
    }, 
    render: function() { 
     return this.transferPropsTo(
      <select>{this.state.options}</select> 
     ) 
    } 
}); 

L'esempio sopra mostra un componente principale MyParent tra cui un componente figlio MySelect cui passa un URL per, insieme a tutti gli altri oggetti di scena che sono validi per un standard reagisce elemento <select>. Usando la funzione incorporata react's this.transferPropsTo() per trasferire tutti gli oggetti di scena all'elemento <select> del bambino nella sua funzione di rendering.

Ciò significa che il genitore che definisce changeHandler come gestore onChange per MySelect passa questa funzione direttamente al <select> elemento del bambino. Quindi, quando la selezione cambia, l'evento viene gestito dal genitore anziché dal bambino.E dal modo in cui questo è un modo legittimo e legittimo per fare le cose in React - dal momento che sta ancora seguendo la filosofia top down. Se ci pensate, quando usate un normale <select> integrato per reagire, questo è esattamente quello che sta succedendo. onChange è solo una proprietà per <select>.

Vale anche la pena notare che, se lo desideri, puoi "hyjack" il gestore delle modifiche per aggiungere la tua logica personalizzata. Ad esempio, quello che tendo a fare è racchiudere i miei campi <input type="text" /> con un componente di input personalizzato che prende tutti gli oggetti di scena validi <input>, ma accetta anche la funzione validator come una proprietà che mi consente di definire una funzione che restituisce vero/falso in base al valore immesso nel campo <input>. Posso farlo definendo onChange nel genitore, per il bambino del wrapper, ma poi nel figlio definisco internamente il mio onChange che controlla lo this.props.onChange da definire e una funzione, quindi esegue il mio validatore e passa il backup della catena di eventi al genitore chiamando lo this.props.onChange(e, valid). Dando al genitore lo stesso oggetto evento nel suo gestore onChange, ma anche con un argomento booleano aggiunto valid. Comunque, sto divagando ....

Spero che questo ti aiuta :)

+1

Mike, come scritto la soluzione non invocherà il rendering per l'oggetto di selezione in quanto non si usa (x) setState. Ho modificato il codice come segue per creare un array di posizione, var optionsArray = [] ;, quindi aggiorna quell'array, infine dopo il ciclo da richiamare: this.setState ({options: optionsArray}); –

+0

Buona cattura - questo è quello che ottengo per non testare il codice nelle risposte, anche se personalmente preferisco usare 'this.forceUpdate()' quando si modifica direttamente l'oggetto stato. Per lo più solo preferenza personale però. Ho modificato la mia risposta per aggiornarla. –