React input defaultValue não atualiza com o estado

94

Estou tentando criar um formulário simples com react, mas estou enfrentando dificuldade para vincular os dados corretamente ao defaultValue do formulário.

O comportamento que procuro é este:

  1. Quando abro minha página, o campo de entrada de texto deve ser preenchido com o texto da minha AwayMessage em meu banco de dados. Isso é "Texto de amostra"
  2. Idealmente, eu quero ter um espaço reservado no campo de entrada Texto se AwayMessage em meu banco de dados não tiver texto.

No entanto, agora, estou descobrindo que o campo de entrada Texto fica em branco sempre que atualizo a página. (Embora o que eu digito na entrada salve corretamente e persista.) Acho que isso ocorre porque o html do campo de texto de entrada carrega quando o AwayMessage é um objeto vazio, mas não é atualizado quando o awayMessage é carregado. Além disso, não consigo especificar um valor padrão para o campo.

Removi parte do código para maior clareza (ou seja, 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>

meu console.log para AwayMessage mostra o seguinte:

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

Respostas:

61

defaultValue é apenas para o carregamento inicial

Se você quiser inicializar a entrada, deverá usar defaultValue, mas se quiser usar o estado para alterar o valor, será necessário usar o valor. Pessoalmente, gosto de apenas usar defaultValue se estou apenas inicializando-o e, em seguida, apenas usar refs para obter o valor quando eu enviar. Há mais informações sobre refs e entradas nos documentos react, https://facebook.github.io/react/docs/forms.html e https://facebook.github.io/react/docs/working-with-the- browser.html .

Eis como eu reescreveria sua entrada:

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

Além disso, você não deseja passar um texto de espaço reservado como fez, porque isso na verdade definirá o valor como 'texto de espaço reservado'. Você ainda precisa passar um valor em branco para a entrada porque undefined e nil transforma o valor em defaultValue essencialmente. https://facebook.github.io/react/tips/controlled-input-null-value.html .

getInitialState não pode fazer chamadas de API

Você precisa fazer chamadas de API depois que getInitialState for executado. Para o seu caso, eu faria isso em componentDidMount. Siga este exemplo, https://facebook.github.io/react/tips/initial-ajax.html .

Eu também recomendo ler sobre o ciclo de vida do componente com o react. https://facebook.github.io/react/docs/component-specs.html .

Reescrever com modificações e estado de carregamento

Pessoalmente, não gosto de fazer tudo, senão a lógica na renderização e prefiro usar 'loading' no meu estado e renderizar uma fonte incrível giratória antes que o formulário carregue, http://fortawesome.github.io/Font- Incrível / exemplos / . Aqui está uma reescrita para mostrar o que quero dizer. Se eu confundi os carrapatos do cjsx, é porque normalmente uso apenas o coffeescript assim,.

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>

Isso deveria cobrir tudo. Essa é uma maneira de lidar com formulários que usam estado e valor. Você também pode usar defaultValue em vez de value e então usar refs para obter os valores ao enviar. Se você seguir esse caminho, eu recomendaria que você tivesse um componente de casca externa (geralmente referido como componentes de alta ordem) para buscar os dados e depois passá-los para o formulário como adereços.

No geral, eu recomendo a leitura dos documentos reactivos por completo e alguns tutoriais. Existem muitos blogs por aí e http://www.egghead.io teve alguns bons tutoriais. Também tenho algumas coisas no meu site, http://www.openmindedinnovations.com .

Blaine Hatab
fonte
Só por curiosidade, por que não adianta fazer chamadas de API no estado inicial? getInitialState é executado logo antes de componentDidMount, e a chamada de API é assíncrona. Isso é mais convencional ou há outra razão por trás disso?
Mïchael Makaröv
1
Não sei exatamente onde li, mas sei que você não faz chamadas de API lá. Existe uma biblioteca que foi feita para lidar com isso, github.com/andreypopp/react-async . Mas eu não usaria essa biblioteca e apenas a colocaria em componentDidMount. Eu sei que no tutorial sobre a documentação reats, a API chama em componentDidMount também.
Blaine Hatab
@ MïchaelMakaröv --porque as chamadas de API são assíncronas e getInitialState retorna o estado de forma síncrona. Portanto, seu estado inicial será configurado antes que a chamada da API seja concluída.
drogon
2
É seguro substituir defaultValue por value? Eu sei que o defaultValue carrega apenas na inicialização, mas o valor também parece fazer isso.
stealthysnacks
2
@stealthysnacks está ok para usar o valor, mas agora você tem que definir esse valor para que a entrada funcione. defaultValue apenas define o valor inicial e a entrada poderá mudar, mas ao usar o valor, ele agora é 'controlado'
Blaine Hatab
60

Outra maneira de corrigir isso é alterando o keyda entrada.

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

Atualização: Já que isso ganhou votos positivos, devo dizer que você deve ter um suporte disabledou readonlyprop enquanto o conteúdo está carregando, para não diminuir a experiência de ux.

E sim, provavelmente é um hack, mas dá conta do recado ... ;-)

Tentando Melhorar
fonte
Implementação ingênua - o foco de um campo de entrada mudando enquanto o valor da chave é alterado (saiu em KeyUp, por exemplo)
Arthur Kushman
2
Sim, tem algumas desvantagens, mas realiza o trabalho facilmente.
TryingToImprove
Isso é inteligente. Eu usei isso selectcom keyo defaultValueque realmente é value.
Avi
alterar o keyda entrada é a chave para obter o novo valor refletido na entrada. Eu usei com type="text"sucesso.
Jacob Nelson
3

Talvez não seja a melhor solução, mas eu faria um componente como o abaixo para poder reutilizá-lo em todo o meu código. Eu gostaria que já estivesse em reação por padrão.

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

O componente pode ser semelhante 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()}
    />`

Onde mudança por array é uma função que acessa o hash por um caminho expresso por um 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 
Mïchael Makaröv
fonte
0

A maneira mais confiável de definir valores iniciais é usar componentDidMount () {} além 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; 
}
...

Você pode achar fácil destruir um elemento e substituí-lo por um novo com

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

como isso:

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

Você também pode usar esta versão do método de desmontagem:

ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode);
romano
fonte
4
Você está manipulando o DOM aqui ... não é um grande NÃO NÃO em reação?
Devashish de
0

Dê valor ao parâmetro "placeHolder". Por exemplo :-

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