Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React input defaultValue doesn't update with state

I'm trying to create a simple form with react, but facing difficulty having the data properly bind to the defaultValue of the form.

The behavior I'm looking for is this:

  1. When I open my page, the Text input field should be filled in with the text of my AwayMessage in my database. That is "Sample Text"
  2. Ideally I want to have a placeholder in the Text input field if the AwayMessage in my database has no text.

However, right now, I'm finding that the Text input field is blank every time I refresh the page. (Though what I type into the input does save properly and persist.) I think this is because the input text field's html loads when the AwayMessage is an empty object, but doesn't refresh when the awayMessage loads. Also, I'm unable to specify a default value for the field.

I removed some of the code for clarity (i.e. 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> 

my console.log for AwayMessage shows the following:

AWAY_MESSAGE Object {} AWAY_MESSAGE Object {id: 1, company_id: 1, text: "Sample Text", master_toggle: false} 
like image 395
Neeharika Bhartiya Avatar asked May 09 '15 23:05

Neeharika Bhartiya


People also ask

Why is React not updating on state change?

State updates in React are asynchronous; when an update is requested, there is no guarantee that the updates will be made immediately. The updater functions enqueue changes to the component state, but React may delay the changes, updating several components in a single pass.

How do I change the defaultValue in React?

You can make the input conditionally and then every time you want to force an update of the defaultValue you just need to unmount the input and then immediately render it again.

Can't change input value React?

To solve the issue of being unable to type in an input field in React, make sure to use the defaultValue prop instead of value for uncontrolled input fields. Alternatively, set an onChange prop on the field and handle the change event. Here is an example of how the issue occurs.

How do you update a state variable in React?

To update our state, we use this. setState() and pass in an object. This object will get merged with the current state.


2 Answers

Another way of fixing this is by changing the key of the input.

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

Update: Since this get upvotes, I will have to say that you should properly have a disabled or readonly prop while the content is loading, so you don't decrease the ux experience.

And yea, it is most likely a hack, but it gets the job done.. ;-)

like image 118
TryingToImprove Avatar answered Sep 26 '22 23:09

TryingToImprove


defaultValue is only for the initial load

If you want to initialize the input then you should use defaultValue, but if you want to use state to change the value then you need to use value. Personally I like to just use defaultValue if I'm just initializing it and then just use refs to get the value when I submit. There's more info on refs and inputs on the react docs, https://facebook.github.io/react/docs/forms.html and https://facebook.github.io/react/docs/working-with-the-browser.html.

Here's how I would rewrite your input:

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

Also you don't want to pass placeholder text like you did because that will actually set the value to 'placeholder text'. You do still need to pass a blank value into the input because undefined and nil turns value into defaultValue essentially. https://facebook.github.io/react/tips/controlled-input-null-value.html.

getInitialState can't make api calls

You need to make api calls after getInitialState is run. For your case I would do it in componentDidMount. Follow this example, https://facebook.github.io/react/tips/initial-ajax.html.

I'd also recommend reading up on the component lifecycle with react. https://facebook.github.io/react/docs/component-specs.html.

Rewrite with modifications and loading state

Personally I don't like to do the whole if else then logic in the render and prefer to use 'loading' in my state and render a font awesome spinner before the form loads, http://fortawesome.github.io/Font-Awesome/examples/. Here's a rewrite to show you what I mean. If I messed up the ticks for cjsx, it's because I normally just use coffeescript like this, .

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> 

That should about cover it. Now that is one way to go about forms which uses state and value. You can also just use defaultValue instead of value and then use refs to get the values when you submit. If you go that route I would recommend you have an outer shell component (usually referred to as high order components) to fetch the data and then pass it to the form as props.

Overall I'd recommend reading the react docs all the way through and do some tutorials. There's lots of blogs out there and http://www.egghead.io had some good tutorials. I have some stuff on my site as well, http://www.openmindedinnovations.com.

like image 26
Blaine Hatab Avatar answered Sep 22 '22 23:09

Blaine Hatab