React input defaultValue не обновляется с учетом состояния

99

Я пытаюсь создать простую форму с помощью response, но сталкиваюсь с трудностями при правильной привязке данных к defaultValue формы.

Я ищу следующее поведение:

  1. Когда я открываю свою страницу, поле ввода текста должно быть заполнено текстом моего сообщения AwayMessage в моей базе данных. Это «Образец текста»
  2. В идеале я хочу иметь заполнитель в поле ввода текста, если AwayMessage в моей базе данных не имеет текста.

Однако прямо сейчас я обнаруживаю, что поле ввода текста пусто каждый раз, когда я обновляю страницу. (Хотя то, что я ввожу во ввод, действительно сохраняется и сохраняется.) Я думаю, это потому, что html текстового поля ввода загружается, когда AwayMessage является пустым объектом, но не обновляется при загрузке awayMessage. Кроме того, я не могу указать значение по умолчанию для поля.

Я удалил часть кода для ясности (например, 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>

мой console.log для AwayMessage показывает следующее:

AWAY_MESSAGE Object {}
AWAY_MESSAGE Object {id: 1, company_id: 1, text: "Sample Text", master_toggle: false}
Нихарика Бхартия
источник

Ответы:

63

defaultValue предназначен только для начальной загрузки

Если вы хотите инициализировать ввод, вам следует использовать defaultValue, но если вы хотите использовать состояние для изменения значения, вам нужно использовать значение. Лично мне нравится просто использовать defaultValue, если я просто инициализирую его, а затем просто использую refs для получения значения при отправке. Дополнительная информация о ссылках и вводных данных содержится в документации по реакции, https://facebook.github.io/react/docs/forms.html и https://facebook.github.io/react/docs/working-with-the- browser.html .

Вот как я бы переписал ваш ввод:

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

Также вы не хотите передавать текст-заполнитель, как вы, потому что это фактически установит значение «текст-заполнитель». Вам все равно нужно передать пустое значение во вход, потому что undefined и nil по существу превращают значение в defaultValue. https://facebook.github.io/react/tips/controlled-input-null-value.html .

getInitialState не может выполнять вызовы API

Вам нужно сделать вызовы api после запуска getInitialState. В вашем случае я бы сделал это в componentDidMount. Следуйте этому примеру https://facebook.github.io/react/tips/initial-ajax.html .

Я также рекомендую ознакомиться с жизненным циклом компонентов с помощью react. https://facebook.github.io/react/docs/component-specs.html .

Перепишите с изменениями и состоянием загрузки

Лично мне не нравится делать все, если иначе, то логика в рендере, и я предпочитаю использовать «загрузку» в моем состоянии и визуализировать потрясающий счетчик шрифтов до загрузки формы, http://fortawesome.github.io/Font- Замечательно / примеры / . Вот переписанный текст, чтобы показать вам, что я имею в виду. Если я испортил галочки для cjsx, это потому, что я обычно просто использую coffeescript, например,.

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>

Это должно покрыть это. Это один из способов использования форм, использующих состояние и значение. Вы также можете просто использовать defaultValue вместо value, а затем использовать refs для получения значений при отправке. Если вы пойдете по этому пути, я бы порекомендовал вам иметь компонент внешней оболочки (обычно называемый компонентами высокого порядка) для извлечения данных, а затем передачи их в форму в качестве свойств.

В целом я бы рекомендовал полностью прочитать документацию по реакции и сделать несколько руководств. Там много блогов, и на http://www.egghead.io есть несколько хороших руководств. У меня также есть кое-что на моем сайте http://www.openmindedinnovations.com .

Блейн Хатаб
источник
Просто любопытно, почему бесполезно делать вызовы api в начальном состоянии? getInitialState запускается прямо перед componentDidMount, а вызов API является асинхронным. Это более условно или есть другая причина?
Микаэль Макаров
1
Я не знаю точно, где я это читал, но я знаю, что вы не помещаете туда вызовы api. Для этого была создана библиотека, github.com/andreypopp/react-async . Но я бы не стал использовать эту библиотеку, а просто поместил бы ее в componentDidMount. Я знаю, что в учебнике по документации по реакциям вызов api также выполняется в componentDidMount.
Блейн Хатаб
@ MïchaelMakaröv - поскольку вызовы api являются асинхронными, а getInitialState синхронно возвращает состояние. Итак, ваше начальное состояние будет настроено до завершения вызова API.
drogon
2
Безопасно ли заменить defaultValue на значение? Я знаю, что defaultValue загружается только при инициализации, но значение, похоже, тоже делает это.
stealthysnacks
2
@stealthysnacks можно использовать значение, но теперь вы должны установить это значение, чтобы вход даже работал. defaultValue просто устанавливает начальное значение, и ввод может изменяться, но при использовании значения теперь он «контролируется»
Блейн Хатаб
62

Другой способ исправить это - изменить keyвходные данные.

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

Обновление: так как это дает положительные голоса, я должен сказать, что вы должны правильно использовать disabledили readonlyопору во время загрузки контента, чтобы вы не уменьшали опыт ux.

И да, скорее всего, это взлом, но он выполняет свою работу .. ;-)

TryingToImprove
источник
Наивная реализация - фокус поля ввода изменяется при изменении значения ключа (например, не работает на KeyUp)
Артур Кушман,
2
Да, у него есть некоторые недостатки, но он легко выполняет свою работу.
TryingToImprove
Это умно. Я использовал его для selectс keyтак defaultValueчто на самом деле value.
Avi
изменение keyввода - это ключ к отражению нового значения во вводе. Я type="text"успешно использовал его .
Джейкоб Нельсон
3

Возможно, это не лучшее решение, но я бы сделал компонент, как показано ниже, чтобы я мог повторно использовать его везде в своем коде. Я бы хотел, чтобы он уже был включен по умолчанию.

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

Компонент может выглядеть так:

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

Где изменение по массиву - это функция, обращающаяся к хешу по пути, выраженному массивом

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

Самый надежный способ установить начальные значения - использовать componentDidMount () {} в дополнение к render () {}:

... 
componentDidMount(){

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

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

Вы можете легко уничтожить элемент и заменить его новым с помощью

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

как это:

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

Вы также можете использовать эту версию метода размонтирования:

ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode);
Римский
источник
5
Вы сами здесь манипулируете DOM ... разве это не большой ответ?
Devashish
0

Придайте значение параметру «placeHolder». Например :-

 <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}
    />
Манас Гонд
источник