React input defaultValue doesn't update with state

后端 未结 5 1610
孤街浪徒
孤街浪徒 2020-11-28 05:27

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

相关标签:
5条回答
  • 2020-11-28 06:09

    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.

    0 讨论(0)
  • 2020-11-28 06:10

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

    0 讨论(0)
  • 2020-11-28 06:13

    The most reliable way to set initial values is to use componentDidMount(){} in addition to render(){}:

    ... 
    componentDidMount(){
    
        const {nameFirst, nameSecond, checkedStatus} = this.props;
    
        document.querySelector('.nameFirst').value          = nameFirst;
        document.querySelector('.nameSecond').value         = nameSecond;
        document.querySelector('.checkedStatus').checked    = checkedStatus;        
        return; 
    }
    ...
    

    You may find it easy to destroy an element and replacing it with the new one with

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

    like this:

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

    You can use also this version of unmount method:

    ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode);
    
    0 讨论(0)
  • 2020-11-28 06:13

    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}
        />
    
    0 讨论(0)
  • 2020-11-28 06:18

    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 
    
    0 讨论(0)
提交回复
热议问题