React input defaultValue non si aggiorna con lo stato


94

Sto cercando di creare un modulo semplice con React, ma ho difficoltà a far associare correttamente i dati al valore predefinito del modulo.

Il comportamento che cerco è questo:

  1. Quando apro la mia pagina, il campo di immissione del testo dovrebbe essere riempito con il testo del mio AwayMessage nel mio database. Questo è "Testo di esempio"
  2. Idealmente, desidero avere un segnaposto nel campo di immissione del testo se AwayMessage nel mio database non ha testo.

Tuttavia, in questo momento, sto riscontrando che il campo di immissione del testo è vuoto ogni volta che aggiorno la pagina. (Anche se ciò che digito nell'input salva correttamente e persiste.) Penso che ciò sia dovuto al fatto che l'html del campo di testo di input viene caricato quando AwayMessage è un oggetto vuoto, ma non si aggiorna quando viene caricato awayMessage. Inoltre, non sono in grado di specificare un valore predefinito per il campo.

Ho rimosso parte del codice per chiarezza (ad esempio onToggleChange)

    window.Pages ||= {}

    Pages.AwayMessages = React.createClass

      getInitialState: ->
        App.API.fetchAwayMessage (data) =>
        @setState awayMessage:data.away_message
        {awayMessage: {}}

      onTextChange: (event) ->
        console.log "VALUE", event.target.value

      onSubmit: (e) ->
        window.a = @
        e.preventDefault()
        awayMessage = {}
        awayMessage["master_toggle"]=@refs["master_toggle"].getDOMNode().checked
        console.log "value of text", @refs["text"].getDOMNode().value
        awayMessage["text"]=@refs["text"].getDOMNode().value
        @awayMessage(awayMessage)

      awayMessage: (awayMessage)->
        console.log "I'm saving", awayMessage
        App.API.saveAwayMessage awayMessage, (data) =>
          if data.status == 'ok'
            App.modal.closeModal()
            notificationActions.notify("Away Message saved.")
            @setState awayMessage:awayMessage

      render: ->
        console.log "AWAY_MESSAGE", this.state.awayMessage
        awayMessageText = if this.state.awayMessage then this.state.awayMessage.text else "Placeholder Text"
        `<div className="away-messages">
           <div className="header">
             <h4>Away Messages</h4>
           </div>
           <div className="content">
             <div className="input-group">
               <label for="master_toggle">On?</label>
               <input ref="master_toggle" type="checkbox" onChange={this.onToggleChange} defaultChecked={this.state.awayMessage.master_toggle} />
             </div>
             <div className="input-group">
               <label for="text">Text</label>
               <input ref="text" onChange={this.onTextChange} defaultValue={awayMessageText} />
             </div>
           </div>
           <div className="footer">
             <button className="button2" onClick={this.close}>Close</button>
             <button className="button1" onClick={this.onSubmit}>Save</button>
           </div>
         </div>

il mio console.log per AwayMessage mostra quanto segue:

AWAY_MESSAGE Object {}
AWAY_MESSAGE Object {id: 1, company_id: 1, text: "Sample Text", master_toggle: false}

Risposte:


61

defaultValue è solo per il caricamento iniziale

Se si desidera inizializzare l'input, è necessario utilizzare defaultValue, ma se si desidera utilizzare state per modificare il valore, è necessario utilizzare value. Personalmente mi piace usare solo defaultValue se lo sto solo inizializzando e quindi usare solo refs per ottenere il valore quando invio. Ci sono più informazioni su ref e input sui documenti di react, https://facebook.github.io/react/docs/forms.html e https://facebook.github.io/react/docs/working-with-the- browser.html .

Ecco come riscriverei il tuo input:

awayMessageText = if this.state.awayMessage then this.state.awayMessage.text else ''
<input ref="text" onChange={this.onTextChange} placeholder="Placeholder Text" value={@state.awayMessageText} />

Inoltre, non vuoi passare il testo segnaposto come hai fatto perché questo imposterà effettivamente il valore su "testo segnaposto". È comunque necessario passare un valore vuoto nell'input perché undefined e nil trasforma essenzialmente il valore in defaultValue. https://facebook.github.io/react/tips/controlled-input-null-value.html .

getInitialState non può effettuare chiamate API

È necessario effettuare chiamate API dopo aver eseguito getInitialState. Per il tuo caso lo farei in componentDidMount. Segui questo esempio, https://facebook.github.io/react/tips/initial-ajax.html .

Consiglio anche di leggere il ciclo di vita dei componenti con React. https://facebook.github.io/react/docs/component-specs.html .

Riscrivi con modifiche e stato di caricamento

Personalmente non mi piace fare il tutto se non altro la logica nel rendering e preferisco usare 'caricamento' nel mio stato e rendere un fantastico spinner di font prima che il modulo venga caricato, http://fortawesome.github.io/Font- Fantastico / esempi / . Ecco una riscrittura per mostrarti cosa intendo. Se ho incasinato le zecche per cjsx, è perché normalmente uso solo coffeescript in questo modo,.

window.Pages ||= {}

Pages.AwayMessages = React.createClass

  getInitialState: ->
    { loading: true, awayMessage: {} }

  componentDidMount: ->
    App.API.fetchAwayMessage (data) =>
      @setState awayMessage:data.away_message, loading: false

  onToggleCheckbox: (event)->
    @state.awayMessage.master_toggle = event.target.checked
    @setState(awayMessage: @state.awayMessage)

  onTextChange: (event) ->
    @state.awayMessage.text = event.target.value
    @setState(awayMessage: @state.awayMessage)

  onSubmit: (e) ->
    # Not sure what this is for. I'd be careful using globals like this
    window.a = @
    @submitAwayMessage(@state.awayMessage)

  submitAwayMessage: (awayMessage)->
    console.log "I'm saving", awayMessage
    App.API.saveAwayMessage awayMessage, (data) =>
      if data.status == 'ok'
        App.modal.closeModal()
        notificationActions.notify("Away Message saved.")
        @setState awayMessage:awayMessage

  render: ->
    if this.state.loading
      `<i className="fa fa-spinner fa-spin"></i>`
    else
    `<div className="away-messages">
       <div className="header">
         <h4>Away Messages</h4>
       </div>
       <div className="content">
         <div className="input-group">
           <label for="master_toggle">On?</label>
           <input type="checkbox" onChange={this.onToggleCheckbox} checked={this.state.awayMessage.master_toggle} />
         </div>
         <div className="input-group">
           <label for="text">Text</label>
           <input ref="text" onChange={this.onTextChange} value={this.state.awayMessage.text} />
         </div>
       </div>
       <div className="footer">
         <button className="button2" onClick={this.close}>Close</button>
         <button className="button1" onClick={this.onSubmit}>Save</button>
       </div>
     </div>

Dovrebbe coprirlo. Questo è un modo per affrontare le forme che usano stato e valore. Puoi anche usare solo defaultValue invece di value e quindi usare refs per ottenere i valori quando invii. Se segui questa strada ti consiglierei di avere un componente di shell esterno (di solito indicato come componenti di ordine elevato) per recuperare i dati e quindi passarli al modulo come oggetti di scena.

Nel complesso, consiglierei di leggere i documenti di reazione fino in fondo e di fare alcuni tutorial. Ci sono molti blog là fuori e http://www.egghead.io ha avuto alcuni buoni tutorial. Ho anche alcune cose sul mio sito, http://www.openmindedinnovations.com .


Solo curioso perché non va bene fare chiamate API nello stato iniziale get? getInitialState viene eseguito subito prima di componentDidMount e la chiamata API è asincrona. È più convenzionale o c'è un'altra ragione dietro?
Mïchael Makaröv

1
Non so esattamente dove l'ho letto ma so che non ci metti chiamate API. C'è una libreria che è stata creata per affrontarlo, github.com/andreypopp/react-async . Ma non userei quella libreria e la posizionerei semplicemente in componentDidMount. So che nel tutorial sulla documentazione sulle reazioni viene eseguita anche la chiamata api in componentDidMount.
Blaine Hatab

@ MïchaelMakaröv: perché le chiamate api sono asincrone e getInitialState restituisce lo stato in modo sincrono. Quindi, il tuo stato iniziale verrà impostato prima che la chiamata api sia completata.
drogon

2
È sicuro sostituire defaultValue con value? So che defaultValue viene caricato solo durante l'inizializzazione, ma anche il valore sembra farlo.
Stealthysnacks

2
@stealthysnacks va bene usare il valore ma ora devi impostare quel valore affinché l'input funzioni. defaultValue imposta solo il valore iniziale e l'input sarà in grado di cambiare ma quando si usa il valore ora è 'controllato'
Blaine Hatab

60

Un altro modo per risolvere questo problema è modificare keyl'input.

<input ref="text" key={this.state.awayMessage ? 'notLoadedYet' : 'loaded'} onChange={this.onTextChange} defaultValue={awayMessageText} />

Aggiornamento: poiché questo ottiene voti positivi, dovrò dire che dovresti avere correttamente un prop disabledo readonlydurante il caricamento del contenuto, in modo da non diminuire l'esperienza di ux.

E sì, molto probabilmente è un hack, ma porta a termine il lavoro .. ;-)


Implementazione ingenua: il focus di un campo di input cambia mentre il valore della chiave viene modificato (uscito su KeyUp per esempio)
Arthur Kushman

2
Sì, ha alcuni inconvenienti, ma fa il lavoro facilmente.
TryingToImprove

Questo è intelligente. L'ho usato per selectcon keyquanto defaultValueche è in realtà value.
Avi

cambiare il valore keydell'input è la chiave per ottenere il nuovo valore riflesso nell'input. L'ho usato con type="text"successo.
Jacob Nelson,

3

Forse non è la soluzione migliore, ma creerei un componente come di seguito in modo da poterlo riutilizzare ovunque nel mio codice. Vorrei che fosse già in reazione per impostazione predefinita.

<MagicInput type="text" binding={[this, 'awayMessage.text']} />

Il componente può essere simile a:

window.MagicInput = React.createClass

  onChange: (e) ->
    state = @props.binding[0].state
    changeByArray state, @path(), e.target.value
    @props.binding[0].setState state

  path: ->
    @props.binding[1].split('.')
  getValue: ->
    value = @props.binding[0].state
    path = @path()
    i = 0
    while i < path.length
      value = value[path[i]]
      i++
    value

  render: ->
    type = if @props.type then @props.type else 'input'
    parent_state = @props.binding[0]
    `<input
      type={type}
      onChange={this.onChange}
      value={this.getValue()}
    />`

Dove change by array è una funzione che accede ad hash tramite un percorso espresso da un array

changeByArray = (hash, array, newValue, idx) ->
  idx = if _.isUndefined(idx) then 0 else idx
  if idx == array.length - 1
    hash[array[idx]] = newValue
  else
    changeByArray hash[array[idx]], array, newValue, ++idx 

0

Il modo più affidabile per impostare i valori iniziali è utilizzare componentDidMount () {} oltre a render () {}:

... 
componentDidMount(){

    const {nameFirst, nameSecond, checkedStatus} = this.props;

    document.querySelector('.nameFirst').value          = nameFirst;
    document.querySelector('.nameSecond').value         = nameSecond;
    document.querySelector('.checkedStatus').checked    = checkedStatus;        
    return; 
}
...

Potresti trovare facile distruggere un elemento e sostituirlo con quello nuovo con

<input defaultValue={this.props.name}/>

come questo:

if(document.querySelector("#myParentElement")){
    ReactDOM.unmountComponentAtNode(document.querySelector("#myParentElement"));
    ReactDOM.render(
        <MyComponent name={name}  />,
        document.querySelector("#myParentElement")
    );
};

Puoi usare anche questa versione del metodo di smontaggio:

ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode);

4
Stai manipolando il DOM da solo qui .. non è un grande NO NO nella reazione?
Devashish,

0

Dai valore al parametro "placeHolder". Per esempio :-

 <input 
    type="text"
    placeHolder="Search product name."
    style={{border:'1px solid #c5c5c5', padding:font*0.005,cursor:'text'}}
    value={this.state.productSearchText}
    onChange={this.handleChangeProductSearchText}
    />
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.