Варіант React input defaultValue не оновлюється зі станом


94

Я намагаюся створити просту форму з реагуванням, але зіткнувся з труднощами, коли дані належним чином прив’язуються до стандартної валути форми.

Поведінка, яку я шукаю:

  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}

Відповіді:


61

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} />

Крім того, ви не хочете передавати текст заповнювача, як ви це робили, тому що це фактично встановить значення "текст заповнювача". Вам все одно потрібно ввести пусте значення у вхід, оскільки невизначене та нульове значення перетворює фактично на defaultValue. https://facebook.github.io/react/tips/control-input-null-value.html .

getInitialState не може робити дзвінки api

Вам потрібно робити дзвінки api після запуску getInitialState. У вашому випадку я б це зробив у компонентіDidMount. Дотримуйтесь цього прикладу https://facebook.github.io/react/tips/initial-ajax.html .

Я також рекомендую ознайомитися з життєвим циклом компонента з реакцією. 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 замість значення, а потім використовувати refs для отримання значень під час надсилання. Якщо ви йдете цим маршрутом, я рекомендую вам мати зовнішній компонент оболонки (зазвичай його називають компонентами високого порядку), щоб отримати дані, а потім передати їх у форму як реквізит.

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


Цікаво, чому не добре робити дзвінки api в початковому стані? getInitialState виконується безпосередньо перед компонентDidMount, а виклик API - це асинхронізація. Це більш умовно чи є інша причина?
Mïchael Makaröv

1
Я точно не знаю, де я це прочитав, але знаю, що ти не робиш туди дзвінки на api. Існує бібліотека, яка була створена для боротьби з нею, github.com/andreypopp/react-async . Але я б не користувався цією бібліотекою, а просто розміщував би її в компонентDidMount. Я знаю, що у підручнику з документацією про реагування api викликає також компонентDidMount.
Блейн Хатаб

@ MïchaelMakaröv - тому що виклики api є асинхронними, а getInitialState повертає стан синхронно. Отже, ваш початковий стан буде встановлений до завершення виклику api.
дрон

2
Чи безпечно замінити defaultValue значенням? Я знаю, що defaultValue завантажується лише при ініціалізації, але, здається, це також робиться.
stealthysnacks

2
@stealthysnacks це нормально використовувати значення, але тепер ви повинні встановити це значення для вхідного рівня, щоб навіть працювати. defaultValue просто встановлює початкове значення, і вхід зможе змінитись, але при використанні значення він тепер "контролюється"
Blaine Hatab

60

Ще один спосіб виправити це шляхом зміни keyвходу.

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

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

І так, це, швидше за все, хак, але це робить роботу .. ;-)


Наївна реалізація - фокус поля введення змінюється при зміні ключового значення (вийшов, наприклад, на KEyUp)
Артур Кушман

2
Так, у цього є деякі недоліки, але робота виконується легко.
TryingToImprove

Це розумно. Я використовував його для selectз keyтак defaultValueщо насправді value.
Аві

зміна 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

Найбільш надійний спосіб встановлення початкових значень - це використання компонентаDidMount () {} на додаток до 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);

4
Ви самі маніпулюєте DOM тут. Хіба це великий НІ НІ в реакції?
Девашиш

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}
    />
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.