React input defaultValue ne se met pas à jour avec l'état


94

J'essaie de créer un formulaire simple avec react, mais je suis confronté à des difficultés pour que les données soient correctement liées à la valeur par défaut du formulaire.

Le comportement que je recherche est le suivant:

  1. Lorsque j'ouvre ma page, le champ de saisie de texte doit être rempli avec le texte de mon AwayMessage dans ma base de données. C'est "Exemple de texte"
  2. Idéalement, je souhaite avoir un espace réservé dans le champ de saisie Texte si le AwayMessage de ma base de données ne contient pas de texte.

Cependant, pour le moment, je constate que le champ de saisie de texte est vide chaque fois que j'actualise la page. (Bien que ce que je tape dans l'entrée enregistre correctement et persiste.) Je pense que c'est parce que le html du champ de texte d'entrée se charge lorsque AwayMessage est un objet vide, mais ne s'actualise pas lorsque le awayMessage se charge. De plus, je ne peux pas spécifier une valeur par défaut pour le champ.

J'ai supprimé une partie du code pour plus de clarté (c'est-à-dire 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>

mon console.log pour AwayMessage montre ce qui suit:

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

Réponses:


61

defaultValue est uniquement pour le chargement initial

Si vous souhaitez initialiser l'entrée, vous devez utiliser defaultValue, mais si vous souhaitez utiliser state pour modifier la valeur, vous devez utiliser value. Personnellement, j'aime simplement utiliser defaultValue si je ne fais que l'initialiser, puis utiliser simplement refs pour obtenir la valeur lorsque je soumets. Il y a plus d'informations sur les références et les entrées sur les documents de réaction, https://facebook.github.io/react/docs/forms.html et https://facebook.github.io/react/docs/working-with-the- browser.html .

Voici comment je réécrirais votre entrée:

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

De plus, vous ne voulez pas transmettre de texte d'espace réservé comme vous l'avez fait, car cela définira en fait la valeur sur «texte d'espace réservé». Vous devez toujours passer une valeur vide dans l'entrée car undefined et nil transforment la valeur en valeur par défaut essentiellement. https://facebook.github.io/react/tips/controlled-input-null-value.html .

getInitialState ne peut pas passer d'appels API

Vous devez passer des appels API après l'exécution de getInitialState. Pour votre cas, je le ferais dans componentDidMount. Suivez cet exemple, https://facebook.github.io/react/tips/initial-ajax.html .

Je recommanderais également de lire le cycle de vie des composants avec react. https://facebook.github.io/react/docs/component-specs.html .

Réécrire avec les modifications et l'état de chargement

Personnellement, je n'aime pas faire le tout sinon la logique dans le rendu et préfère utiliser `` chargement '' dans mon état et rendre une police géniale avant le chargement du formulaire, http://fortawesome.github.io/Font- Génial / exemples / . Voici une réécriture pour vous montrer ce que je veux dire. Si j'ai raté les graduations pour cjsx, c'est parce que j'utilise normalement simplement coffeescript comme celui-ci,.

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>

Cela devrait le couvrir. Maintenant, c'est une façon de traiter les formulaires qui utilisent l'état et la valeur. Vous pouvez également simplement utiliser defaultValue au lieu de value, puis utiliser refs pour obtenir les valeurs lorsque vous soumettez. Si vous suivez cette voie, je vous recommanderais d'avoir un composant de coque externe (généralement appelé composants d'ordre supérieur) pour récupérer les données, puis les transmettre au formulaire en tant qu'éléments.

Dans l'ensemble, je recommanderais de lire les documents de réaction tout au long et de faire quelques tutoriels. Il y a beaucoup de blogs là-bas et http://www.egghead.io avait quelques bons tutoriels. J'ai également des informations sur mon site, http://www.openmindedinnovations.com .


Juste curieux de savoir pourquoi il n'est pas bon de faire des appels API dans l'état initial? getInitialState est exécuté juste avant componentDidMount et l'appel d'API est asynchrone. Est-ce plus conventionnel ou y a-t-il une autre raison derrière cela?
Mïchael Makaröv

1
Je ne sais pas exactement où je l'ai lu mais je sais que vous n'y mettez pas d'appels API. Il y a une bibliothèque qui a été faite pour y faire face, github.com/andreypopp/react-async . Mais je n'utiliserais pas cette bibliothèque et je la placerais simplement dans componentDidMount. Je sais que dans le didacticiel sur la documentation de reacts, l'api appelle également componentDidMount.
Blaine Hatab

@ MïchaelMakaröv - parce que les appels API sont asynchrones et getInitialState renvoie l'état de manière synchrone. Ainsi, votre état initial sera configuré avant la fin de l'appel de l'API.
drogon

2
Est-il sûr de remplacer defaultValue par value? Je sais que defaultValue se charge uniquement lors de l'initialisation, mais la valeur semble également le faire.
stealthysnacks

2
@stealthysnacks vous pouvez utiliser la valeur, mais vous devez maintenant définir cette valeur pour que l'entrée fonctionne même. defaultValue définit simplement la valeur initiale et l'entrée pourra changer, mais lors de l'utilisation de la valeur, elle est maintenant `` contrôlée ''
Blaine Hatab

60

Une autre façon de résoudre ce problème est de changer le keyde l'entrée.

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

Mise à jour: Étant donné que cela suscite des votes positifs, je dois dire que vous devriez correctement avoir un disabledou un readonlyaccessoire pendant le chargement du contenu, afin de ne pas diminuer l'expérience ux.

Et oui, c'est probablement un hack, mais ça fait le travail .. ;-)


Implémentation naïve - le focus d'un champ de saisie change pendant que la valeur de la clé est modifiée (sorti surKeyUp par exemple)
Arthur Kushman

2
Oui, il a quelques inconvénients, mais fait le travail facilement.
TryingToImprove

C'est intelligent. Je l' ai utilisé pour selectavec keycomme ce defaultValuequi est en réalité value.
Avi

changer le keyde l'entrée est la clé pour obtenir la nouvelle valeur reflétée dans l'entrée. Je l'ai utilisé avec type="text"succès.
Jacob Nelson

3

Peut-être pas la meilleure solution, mais je créerais un composant comme ci-dessous afin de pouvoir le réutiliser partout dans mon code. J'aurais aimé qu'il soit déjà en réaction par défaut.

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

Le composant peut ressembler à:

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()}
    />`

Où le changement par tableau est une fonction accédant au hachage par un chemin exprimé par un tableau

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

Le moyen le plus fiable de définir les valeurs initiales consiste à utiliser componentDidMount () {} en plus de render () {}:

... 
componentDidMount(){

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

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

Vous trouverez peut-être facile de détruire un élément et de le remplacer par le nouveau avec

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

comme ça:

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

Vous pouvez également utiliser cette version de la méthode de démontage:

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

5
Vous manipulez vous-même le DOM ici .. n'est-ce pas un grand NON NON en réaction?
Devashish

0

Donnez une valeur au paramètre "placeHolder". Par exemple :-

 <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}
    />
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.