130

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}
R andom
  • 101
  • 9
Neeharika Bhartiya
  • 1,601
  • 2
  • 14
  • 11

10 Answers10

94

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.. ;-)

TryingToImprove
  • 7,047
  • 4
  • 30
  • 39
  • 1
    Naive implementation - the focus of an input field changing while key value is changed (went out onKeyUp for example) – Arthur Kushman Mar 12 '17 at 17:55
  • 2
    Yea, it got some drawbacks, but gets the job done easily. – TryingToImprove Mar 13 '17 at 07:53
  • This is smart. I used it for `select` with `key` as `defaultValue` which is actually `value`. – Avi Dec 13 '18 at 07:38
  • changing the `key` of the input is the key to get the new value reflected in input. I used it with `type="text"` successfully. – Jacob Nelson Oct 25 '19 at 08:00
  • 2
    This approach was [recommended on the React blog](https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key), so it's not a hack. – Noumenon Mar 12 '21 at 13:52
  • 1
    what if the input has an onchange method that updates a redux value? then with every keystroke it also loses focus when having a key – happinin May 03 '22 at 02:32
  • 1
    @happinin if it is the same key across renders then it should not lose focus - however if you generate a key using `Math.random()` or something that changes on each render, then it should lose focus since it get detacted and reattached.. – TryingToImprove May 03 '22 at 11:11
  • 1
    @happinin also the example here is without the `value`-prop, so this should only be used if you are using a uncontrolled component. If you trigger `onChange` with the new value you most likely want a controlled component and setting `value` instead opf `defaultValue` – TryingToImprove May 03 '22 at 11:13
  • 1
    @TryingToImprove I actually worked out this issue. I changed keys from Math.random() to ids and it worked perfect :) – happinin Jun 01 '22 at 11:18
  • default values not changing when the new state value is 0, it takes the Previous state! any suggestions guys? – programmers_view Nov 15 '22 at 07:41
  • 1
    @programmers_view `defaultValue` can only be set once, otherwise you will use a controlled input or changing the key.. – TryingToImprove Nov 15 '22 at 10:03
  • @TryingToImprove got your point man tnx! – programmers_view Nov 17 '22 at 06:55
76

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.

Blaine Hatab
  • 1,626
  • 17
  • 24
  • Just curious why is no good doing api calls in get initial state? getInitialState gets executed right before componentDidMount, and API call is async. Is that more conventional or there's another reason behind it? – Mïchael Makaröv May 11 '15 at 17:38
  • 1
    I don't know exactly where I read it but I know you don't put api calls in there. There's a library that was made to deal with it, https://github.com/andreypopp/react-async. But I wouldn't use that library and would just place it in componentDidMount. I know that in the tutorial on reacts documentation does the api call in componentDidMount as well. – Blaine Hatab May 11 '15 at 20:18
  • @MïchaelMakaröv --because api calls are async, and getInitialState returns state synchronously. So, your initial state will be set up before the api call is completed. – drogon May 12 '16 at 22:53
  • 3
    Is it safe to replace defaultValue with value? I know defaultValue loads only on initialization but value seems to also do this. – stealthysnacks Aug 25 '16 at 19:04
  • 2
    @stealthysnacks it's ok to use value but now you have to set that value for the input to even work. defaultValue just sets the initial value and the input will be able to change but when using value it is now 'controlled' – Blaine Hatab Aug 25 '16 at 19:51
  • You're right, you can't edit the input by changing it to value unless it is a controlled component. – stealthysnacks Aug 25 '16 at 21:36
  • You are wrong. `defaultValue` works well for `type=text` but doesn't work for `select`s – Green Mar 21 '18 at 22:23
31

it's extremely simple, make defaultValue and key the same:

<input defaultValue={myVal} key={myVal}/>

This is one of the recommended approaches at https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key

Michael Osofsky
  • 11,429
  • 16
  • 68
  • 113
Jar
  • 1,766
  • 1
  • 21
  • 27
  • 2
    it's actually not that simple. when a user goes to type in another value for the input field, it loses focus every keystroke if you have an onchange method updating redux. – happinin May 03 '22 at 02:29
  • is doesn't update when the new state value is 0, it still takes the previous state value, Any Suggestion guyz? – programmers_view Nov 14 '22 at 10:58
5

To force the defaultValue to re-render all you need to do is change the key value of the input itself. here is how you do it.

<input 
type="text" 
key={myDynamicKey} 
defaultValue={myDynamicDefaultValue} 
placeholder="It works"/>
jerryurenaa
  • 3,863
  • 1
  • 27
  • 17
  • 2
    Doesn't work. Where do you define `{myDynamicKey}` ? – gene b. May 28 '21 at 20:04
  • 1
    Be careful with dynamic keys. They can cause your input to lose focus every key press: https://stackoverflow.com/a/59287017/2218580 – TreeAndLeaf Feb 07 '22 at 23:37
  • This solution worked great for me. I set my key to the same value as my defaultValue, which is determined through a ternary operator that renders asynchronously. However, why does updating the key cause a re-render instead of when I change my defaultValue? – boredProjects Aug 31 '23 at 21:42
3

Maybe not the best solution, but I'd make a component like below so I can reuse it everywhere in my code. I wish it was already in react by default.

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

The component may look like:

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

Where change by array is a function accessing hash by a path expressed by an 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
  • 1,085
  • 12
  • 21
3

Related issue

Setting defaulValue on control din't not update the state.

Doing reverse works perfectly:

  • Set state to default value, and the control UI gets updated correctly as if defaulValue was given.

Code:

let defaultRole = "Owner";

const [role, setRole] = useState(defaultRole);

useEffect(() => {
    setMsg(role);
});

const handleChange = (event) => {
    setRole(event.target.value );
};

// ----

<TextField
    label="Enter Role"
    onChange={handleChange}
    autoFocus
    value={role}
/>
Manohar Reddy Poreddy
  • 25,399
  • 9
  • 157
  • 140
1
  1. Define a state for your default value
  2. Surround your input with a div and a key prop
  3. Set the key value to the same value as the defaultValue of the input.
  4. Call your setDefaultValue defined at the step 1 somewhere to re-render your component

Example:

const [defaultValue, setDefaultValue] = useState(initialValue);

useEffect(() => {
    setDefaultValue(initialValue);
}, false)

return (
    <div key={defaultValue}>
        <input defaultValue={defaultValue} />
    </div>
)
Nicolas Bodin
  • 1,431
  • 1
  • 20
  • 25
0

Give value to parameter "placeHolder". For example :-

 <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}
    />
4b0
  • 21,981
  • 30
  • 95
  • 142
Manas Gond
  • 640
  • 7
  • 13
0

If you want to render dynamic data in the form value then you have to use:

    setValue("fname", "Bilal");
    setValue("lname", "Khan");
    setValue("address", "Pakistan");

from react hook form. This will dynamically change your data on click or on some event in the form value field.

ahuemmer
  • 1,653
  • 9
  • 22
  • 29
-1

Use value instead of defaultValue and change the value of the input with the onChange method.

Gabriel Arghire
  • 1,992
  • 1
  • 21
  • 34