Change input defaultValue by passing props
Asked Answered
U

9

59

Consider this example:

var Field = React.createClass({
    render: function () {
        // never renders new value...
        return (
            <div>
                <input type="text" defaultValue={this.props.value || ''} />
            </div>
        );
    }
});

var App = React.createClass({
    getInitialState: function () {
        return {value: 'Hello!'};
    },

    changeTo: function (str) {
        this.setState({value: str});
    },

    render: function () {
        return (
            <div>
                <Field value={this.state.value} />
                <button onClick={this.changeTo.bind(null, 'Whyyyy?')}>Change to "Whyyyy?"</button>
                <button onClick={this.changeTo.bind(null, void 0)}>Change to undefined</button>
            </div>
        );
    }
});

React.render(
    <App />,
    document.getElementById('app')
);

I want to pass value into defaultValue as prop of dumb input component. However it never re-renders it.

Unbar answered 9/6, 2015 at 9:15 Comment(2)
Read facebook.github.io/react/docs/forms.html#advanced-topics why this is happening.Doncaster
Possible duplicate of React input defaultValue doesn't update with stateAntevert
S
133

As a previous answer mentioned, defaultValue only gets set on initial load for a form. After that, it won't get "naturally" updated because the intent was only to set an initial default value.

You can get around this if you need to by passing a key to the wrapper component, like on your Field or App component, though in more practical circumstances, it would probably be a form component. A good key would be a unique value for the resource being passed to the form - like the id stored in the database, for example.

In your simplified case, you could do this in your Field render:

<div key={this.props.value}>
    <input type="text" defaultValue={this.props.value || ''} />
</div>

In a more complex form case, something like this might get what you want if for example, your onSubmit action submitted to an API but stayed on the same page:

const Form = ({item, onSubmit}) => {
  return (
    <form onSubmit={onSubmit} key={item.id}>
      <label>
        First Name
        <input type="text" name="firstName" defaultValue={item.firstName} />
      </label>
      <label>
        Last Name
        <input type="text" name="lastName" defaultValue={item.lastName} />
      </label>
      <button>Submit!</button>
    </form>
  )
}

Form.defaultProps = {
  item: {}
}

Form.propTypes = {
  item: PropTypes.object,
  onSubmit: PropTypes.func.isRequired
}

When using uncontrolled form inputs, we generally don't care about the values until after they are submitted, so that's why it's more ideal to only force a re-render when you really want to update the defaultValues (after submit, not on every change of the individual input).

If you're also editing the same form and fear the API response could come back with different values, you could provide a combined key of something like id plus timestamp.

Sarcastic answered 31/1, 2017 at 16:13 Comment(3)
Using the value as a key is just a hack, it mounts the element as many times as the value changes. Although it could solve your problem it is not the best way to update your input value. Actually it does not update the value but creates a new one (it can lead to memory leaks in extreme cases). It is better to use a controlled input field instead of this solution.Cosmetician
BTW for multiple values you can use: key={[props.values.a, props.values.b]}Phonogram
@Sarcastic key={this.props.value} is the solution. Do you think that it has any performance impact?Wilt
D
20

defaultValue only works for the initial load. After that, it won't get updated. You need to maintain the state for you Field component:

var Field = React.createClass({
    //transfer props to state on load
    getInitialState: function () {
        return {value: this.props.value};
    },
    //if the parent component updates the prop, force re-render
    componentWillReceiveProps: function(nextProps) {
         this.setState({value: nextProps.value});
    },
    //re-render when input changes
    _handleChange: function (e){
        this.setState({value: e.target.value});
    },
    render: function () {
        // render based on state
        return (
            <div>
                <input type="text" onChange={this._handleChange} 
                                   value={this.state.value || ''} />
            </div>
        );
    }
});
Diecious answered 12/5, 2016 at 23:3 Comment(0)
A
6

I'm fairly certain this has to do with Controlled vs. Uncontrolled inputs.

If I understand correctly, since your <input> is Uncontrolled (doesn't define a value attribute), then the value will always resolve to the value that it is initialized with. In this case Hello!.

In order to overcome this issue, you can add a value attribute and set it during the onChange:

var Field = React.createClass({
      render: function () {
          // never renders new value...
          return (
              <div>
                  <input type="text" defaultValue={this.props.default || ''} value={this.props.value} />
              </div>
          );
      }
  });

Here is a plunker showing the change.

Anadiplosis answered 9/6, 2015 at 11:28 Comment(3)
Is this the cleanest way to resolve uncontrolled inputs? Also I don't see any onChange function on your example.Ibarra
@Ibarra uncontrolled components don't need an onChange. Usually, the value is extracted using a ref.Anadiplosis
Splendid, there is a bug with value setting and other keys like onKeyUp/onKeyPress, so if U need, by some circumstances, the onKeyUp event for ex, you should set onChange on the same function, otherwise the value attribute won't be updated.Prentice
G
1

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.

Greatniece answered 23/5, 2018 at 14:33 Comment(1)
How does one do this?Stokes
T
0

The issue is here:

onClick={this.changeTo.bind(null, 'Whyyyy?')}

I'm curious why you bind to null.

You want to bind to 'this', so that changeTo will setState in THIS object.

Try this

<button onClick={this.changeTo.bind(this, 'Whyyyy?')}>Change to "Whyyyy?"</button>
<button onClick={this.changeTo.bind(this, void 0)}>Change to undefined</button>

In Javascript, when a function is called, its called in the scope where it was called from, not where it was written (I know, seems counter intuitive). To ensure it is called in the context you write it, you need to '.bind(this)'.

To learn more about binding and function scope, there are lots of online tutes, (some much better than others) - you might like this one: http://ryanmorr.com/understanding-scope-and-context-in-javascript/

I also recommend using the React Dev tools if you are using firefox or chrome, this way you would have been able to see that state.message was not changing: https://facebook.github.io/react/blog/2015/09/02/new-react-developer-tools.html

Toponymy answered 27/9, 2016 at 3:18 Comment(0)
A
0

Use conditional rendering, then the component will load correct initial value. Something like in this module:

class MenuHeaderInput extends React.Component{
    constructor(props){
        super(props);
        this.handleBlur = this.handleBlur.bind (this);
    }
    handleBlur (e) {
        this.props.menuHeaderUpdate(e.target.value);
    }
    render(){
        if (this.props.menuHeader) {
            return (
                <div className="w3-row w3-margin" onClick = {() => this.props.handleTitleClick (10)}>
                    <div className="w3-third" ><pre></pre></div>
                    <input
                        className = {"w3-third w3-input w3-jumbo " + EDIT_COLOR}                
                        type = "text"
                        defaultValue = {this.props.menuHeader}
                        onBlur = {this.handleBlur}
                    />
                    <div className="w3-third" ><pre></pre></div>                
                </div>
            )
        }
        else {
            return null;
        }
    }
}
Adorne answered 13/4, 2018 at 13:54 Comment(0)
R
0

Related to Sia's excellent answer above: https://mcmap.net/q/327258/-change-input-defaultvalue-by-passing-props.

For my case I had a few ways in which a form could be updated:

  1. users could input values into form fields
  2. An API request allowed users to restore from previous versions
  3. Users could navigate to a filled out form (using queryParams of the URL)
  4. clearing the form fields.
  5. Etc more ways of allowing all the fields or just a single change to happen from user action or websockets.

I found that the easiest way to make sure the state of the form is reflected in its inputs is indeed:

  1. To provide a manually-controlled key prop on the top level of the form or parent element to the form (as long as it is above the inputs in the DOM tree.
  2. When users are typing a key update does not need to happen.
  3. I made the key be a simple formHistoricalVersion and as certain updates external to a user typing/selecting/etc interacting with the form field's values happened I incremented the formHistoricalVersion.
  4. This made sure that the state of the form whether by user action or by API request was in-sync--I had complete control over it.

Other solutions I tried:

  1. While making the API request make the whole form disappear (when loading change to a loading spinner instead of the form). Disadvantage to performance and for clearForm it was a bit crazy to do, but possible with setImmediate to convert the form to a loading spinner when they first clear it, then setting isLoading back to false in the setImmediate.
  2. Adding a key on each input: this worked amazingly, but it had a weird blip whenever users would type so I had to get rid of it.
  3. Putting a static key for the form (field.id) (as suggested by above answer) didn't cover all the use cases I had.

In conclusion, it worked pretty easily to set the key of the form with react/redux, I just would add the equivalent of:

return {
  ...state,
  formFieldState: payload.formFields,
  historicalFormVersion: state.historicalFormVersion + 1
}

This was necessary because I was using some 3rd party libraries and my own Numeric Input that took in value as a prop but used value as a defaultValue:

const NumberDisplay: FunctionComponent = ({ value, setValue }) => (
  <input
    defaultValue={convertToSpecialNumberDisplay(value)}
    onBlur={(e) => convertToSpecialNumberDisplay(e.target.value)}
    onFocus={(e) => convertToNumberFromDisplay(e.target.value)}
    onChange={(e) => setValue(e.target.value)}
  />
)

Approximate Redux of overall Form:

const FullForm: FunctionComponent = () => {
  const dispatch = useDispatch();
  const formState = useState((state) => state.formState);
  const formHistoricalVersion = useState((state) => state.formHistoricalVersion);

  return (
  <form key={formHistoricalVersion}>
    {renderFormFields(formState, dispatch)}
  </form>
  )
}
Roughish answered 19/8, 2021 at 17:20 Comment(0)
S
0

A wrapper component can be used, to test the props, and decide whether to render the input with defaultValue, or render simple input and wait until the props comes.

For example:

class Outer extends React.Component {
    ...
    render() {
        return <>
            {this.props.value && <Inner value={this.props.value} />}
            {this.props.value === undefined && <input />}
        </>;
    }
}

class Inner extends React.Component {
    ...
    render() {
        return <input defaultValue={this.props.value} />;
    }
}
Sissy answered 18/6 at 5:27 Comment(0)
Q
-1

I also face this problem, what I did was to manually update the input value when the props has change. Add this to your Field react class:

componentWillReceiveProps(nextProps){
    if(nextProps.value != this.props.value) {
        document.getElementById(<element_id>).value = nextProps.value
    }
}

You just need to add an id attribute to your element so that it can be located.

Quotha answered 9/8, 2016 at 5:44 Comment(2)
This breaks the React philosophy I thinkFlattop
The componentWillReceiveProps is good. The document.getElementById() can be replaced by ref.Sissy

© 2022 - 2024 — McMap. All rights reserved.