React input defaultValue nie aktualizuje się ze stanem

98

Próbuję utworzyć prosty formularz z funkcją reagowania, ale mam trudności z prawidłowym powiązaniem danych z wartością domyślną formularza.

Zachowanie, którego szukam, jest następujące:

  1. Kiedy otwieram moją stronę, pole tekstowe powinno być wypełnione tekstem mojej AwayMessage w mojej bazie danych. To jest „Przykładowy tekst”
  2. Idealnie byłoby, gdyby w polu wprowadzania tekstu znajdował się symbol zastępczy, jeśli komunikat AwayMessage w mojej bazie danych nie zawiera tekstu.

Jednak w tej chwili stwierdzam, że pole tekstowe jest puste za każdym razem, gdy odświeżam stronę. (Chociaż to, co wpisuję w danych wejściowych, jest zapisywane prawidłowo i utrwalane). Myślę, że dzieje się tak, ponieważ kod HTML pola wejściowego jest ładowany, gdy AwayMessage jest pustym obiektem, ale nie odświeża się, gdy ładuje się awayMessage. Nie mogę również określić wartości domyślnej dla tego pola.

Usunąłem część kodu dla przejrzystości (np. 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>

Mój console.log dla AwayMessage zawiera następujące informacje:

AWAY_MESSAGE Object {}
AWAY_MESSAGE Object {id: 1, company_id: 1, text: "Sample Text", master_toggle: false}
Neeharika Bhartiya
źródło

Odpowiedzi:

63

defaultValue dotyczy tylko początkowego ładowania

Jeśli chcesz zainicjować wejście, powinieneś użyć defaultValue, ale jeśli chcesz użyć stanu do zmiany wartości, musisz użyć value. Osobiście lubię po prostu używać defaultValue, jeśli tylko go inicjuję, a następnie po prostu używać refs, aby uzyskać wartość, kiedy przesyłam. Więcej informacji na temat referencji i danych wejściowych można znaleźć w dokumentach reakcji, https://facebook.github.io/react/docs/forms.html i https://facebook.github.io/react/docs/working-with-the- browser.html .

Oto, jak przepisałbym twoje dane wejściowe:

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

Nie chcesz również przekazywać tekstu zastępczego, tak jak to zrobiłeś, ponieważ spowoduje to ustawienie wartości na „tekst zastępczy”. Nadal musisz przekazywać pustą wartość do wejścia, ponieważ undefined i nil zasadniczo zamienia wartość w defaultValue. https://facebook.github.io/react/tips/controlled-input-null-value.html .

getInitialState nie może wykonywać wywołań API

Po uruchomieniu getInitialState musisz wykonywać wywołania API. W twoim przypadku zrobiłbym to w componentDidMount. Postępuj zgodnie z tym przykładem, https://facebook.github.io/react/tips/initial-ajax.html .

Polecam również zapoznać się z cyklem życia komponentu w aplikacji React. https://facebook.github.io/react/docs/component-specs.html .

Przepisz z modyfikacjami i stanem ładowania

Osobiście nie lubię robić wszystkiego, jeśli inaczej, to logiki w renderowaniu i wolę używać 'loading' w moim stanie i renderować niesamowitą czcionkę przed załadowaniem formularza, http://fortawesome.github.io/Font- Niesamowite / przykłady / . Oto przepis, który pokazuje, o co mi chodzi. Jeśli zepsułem kleszcze dla cjsx, to dlatego, że zwykle używam po prostu coffeescript w ten sposób,.

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>

To powinno to przykryć. To jest jeden ze sposobów podejścia do formularzy, które używają stanu i wartości. Możesz także po prostu użyć defaultValue zamiast value, a następnie użyć referencji, aby uzyskać wartości podczas przesyłania. Jeśli pójdziesz tą drogą, poleciłbym mieć zewnętrzny komponent powłoki (zwykle nazywany komponentami wyższego rzędu), aby pobrać dane, a następnie przekazać je do formularza jako rekwizyty.

Ogólnie rzecz biorąc, polecam przeczytanie do końca dokumentów React i zrobienie kilku samouczków. Istnieje wiele blogów, a http://www.egghead.io ma kilka dobrych tutoriali. Mam też trochę rzeczy na swojej stronie http://www.openmindedinnovations.com .

Blaine Hatab
źródło
Ciekawe, dlaczego nie warto wykonywać wywołań API w stanie początkowym pobierania? getInitialState jest wykonywane tuż przed componentDidMount, a wywołanie API jest asynchroniczne. Czy jest to bardziej konwencjonalne, czy też jest inny powód?
Mïchael Makaröv
1
Nie wiem dokładnie, gdzie to przeczytałem, ale wiem, że nie umieszczasz tam wywołań API. Istnieje biblioteka, która została stworzona, aby sobie z tym poradzić, github.com/andreypopp/react-async . Ale nie użyłbym tej biblioteki i po prostu umieściłbym ją w componentDidMount. Wiem, że w poradniku dotyczącym dokumentacji reaguje również wywołanie api w module componentDidMount.
Blaine Hatab
@ MïchaelMakaröv - ponieważ wywołania API są asynchroniczne, a getInitialState zwraca stan synchronicznie. Twój stan początkowy zostanie ustawiony przed zakończeniem wywołania interfejsu API.
drogon
2
Czy można bezpiecznie zastąpić defaultValue wartością? Wiem, że defaultValue ładuje się tylko podczas inicjalizacji, ale value wydaje się też to robić.
stealthysnacks
2
@stealthysnacks można użyć wartości, ale teraz musisz ustawić tę wartość, aby dane wejściowe działały. defaultValue po prostu ustawia wartość początkową i wejście będzie mogło się zmienić, ale przy użyciu value jest teraz `` kontrolowane ''
Blaine Hatab
61

Innym sposobem rozwiązania tego problemu jest zmiana keywejścia.

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

Aktualizacja: Ponieważ otrzymuję głosy upvotes, będę musiał powiedzieć, że powinieneś odpowiednio mieć disabledlub readonlyprop podczas ładowania treści, więc nie zmniejszasz doświadczenia UX.

I tak, to najprawdopodobniej hack, ale spełnia swoje zadanie .. ;-)

TryingToImprove
źródło
Naiwna implementacja - punkt skupienia pola wejściowego zmienia się podczas zmiany wartości klucza (na przykład wyszedł na KeysUp)
Arthur Kushman
2
Tak, ma pewne wady, ale łatwo wykonuje swoją pracę.
TryingToImprove
To jest sprytne. Użyłem go selectz keyjak defaultValueco jest rzeczywiście value.
Avi,
zmiana keywejścia jest kluczem do uzyskania nowej wartości odzwierciedlonej na wejściu. Użyłem go z type="text"powodzeniem.
Jacob Nelson
3

Może nie jest to najlepsze rozwiązanie, ale zrobiłbym komponent taki jak poniżej, aby móc go ponownie wykorzystać w całym kodzie. Szkoda, że ​​nie było już domyślnie w reakcji.

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

Komponent może wyglądać następująco:

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

Gdzie zmiana przez tablicę jest funkcją uzyskującą dostęp do skrótu za pomocą ścieżki wyrażonej przez tablicę

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
źródło
0

Najbardziej niezawodnym sposobem ustawienia wartości początkowych jest użycie metody componentDidMount () {} oprócz renderowania () {}:

... 
componentDidMount(){

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

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

Może się okazać, że łatwo będzie zniszczyć element i zastąpić go nowym z

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

lubię to:

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

Możesz również użyć tej wersji metody odmontowania:

ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode);
rzymski
źródło
5
Sam tu manipulujesz DOM .. czy to nie jest duże NIE w reakcji?
Devashish
0

Podaj wartość dla parametru „placeHolder”. Na przykład :-

 <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
źródło