web-dev-qa-db-fra.com

React input defaultValue n'est pas mis à jour avec l'état

J'essaie de créer un formulaire simple avec react, mais j'ai du mal à faire en sorte que les données se lient correctement à 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 renseigné avec le texte de mon message Away 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 AwayMessage dans ma base de données ne contient pas de texte.

Cependant, à l'heure actuelle, 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 cela est dû au fait que le code HTML du champ de saisie se charge lorsque AwayMessage est un objet vide, mais ne s'actualise pas lors du chargement de awayMessage. De plus, je ne peux pas spécifier de 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}
79

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 bien utiliser defaultValue si je l'initialise, puis utiliser les références pour obtenir la valeur lors de l'envoi. 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 voudrais réécrire 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} />

En outre, vous ne souhaitez pas transmettre de texte de substitution comme vous l'avez fait, car la valeur sera définie sur "texte de substitution". Vous devez toujours passer une valeur vide dans l'entrée, car indéfini et nil la transforment en valeur par défaut. https://facebook.github.io/react/tips/controlled-input-null-value.html .

getInitialState ne peut pas faire d'appels API

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

Je vous recommande é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 'loading' dans mon état et rendre une police géniale spinner avant le chargement du formulaire, http: //fortawesome.github. io/Font-Awesome/examples / . Voici une réécriture pour vous montrer ce que je veux dire. Si je me suis trompé dans cjsx, c’est parce que j’utilise normalement du café 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 à propos de le couvrir. Maintenant, c’est une façon de traiter les formes qui utilisent l’état et la valeur. Vous pouvez également simplement utiliser defaultValue au lieu de valeur, puis utiliser refs pour obtenir les valeurs lors de la soumission. Si vous choisissez cette voie, je vous recommanderais de disposer d'un composant Shell externe (généralement appelé composants d'ordre élevé) pour extraire les données, puis les transmettre à la fiche sous forme d'accessoires.

Globalement, je vous recommande de lire la documentation de réaction tout au long et de faire quelques tutoriels. Il y a beaucoup de blogs et http://www.egghead.io avait de bons tutoriels. J'ai aussi des articles sur mon site, http://www.openmindedinnovations.com .

59
Blaine Hatab

Une autre solution consiste à modifier le key de l'entrée.

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

Mise à jour: depuis que nous obtenons des votes positifs, je dois dire que vous devez correctement avoir un accessoire disabled ou readonly pendant le chargement du contenu, afin de ne pas diminuer l'expérience ux.

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

49
TryingToImprove

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. Je souhaite qu'il était 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ù 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 
2

La méthode la plus fiable pour 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 pouvez trouver facile de détruire un élément et de le remplacer par le nouveau par

<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 unmount:

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

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}
    />
0
Manas Gond