0
votes

I have this constructor in React component:

constructor() {

        super();

        this.state = {
            info: {
                title: '',
                description: '',
                height: ''
            }
        }

...

And a form with inputs controlled by the state:

    <form onSubmit={this.handleFormSubmit}>
      <label>Title:</label>
      <input type="text" name="title" value={this.state.info.title} onChange={(e) => this.handleChange(e)} />
      <label>Description:</label>
      <input type="text" name="description" value={this.state.info.description} onChange={(e) => this.handleChange(e)} />
...

When I type anything on the form, I guess there's something wrong with my handler, as I get the warning "Warning: A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component."

Checking console, it seems state is updating each property value that is being typed, and removing other properties, while it should remain all of them and only update the changed ones.

Here's my handler:

handleChange(event) {
        let { name, value } = event.target;
        this.setState({
            info: {
                [name]: value
            }
        });
    }
3

3 Answers

3
votes

the one you are using is updating only one props and the others being stripped that causes React shows warning. you can use spreading to keep the others

handleChange(event) {
    let { name, value } = event.target;
    this.setState({
        info: {
            ...this.state.info,
            [name]: value
        }
    });
}
1
votes

There's usually 2 cases which cause such warnings to manifest in my experience.

  1. The initial value of the property in state is undefined and not being changed when updated
  2. The initial value is '' and being changed to null
1
votes

Try this:

handleChange(event) {
        let { name, value } = event.target;
        this.setState(({info}) =>({
            info: {
                [name]: value
            }
        }));
    }

Breaking down the function call:

  • First you have this.setState(updaterFunction). updaterFunction gets called by setState with the previous state as the argument, and that function is expected to return an object with the keys of the state to update (it gets merged shallowly with the previous state).
  • Because setState is only a shallow merge (no matter if you pass it an object or a function), if you have an object at like this.state.foo.bar and you update the state with an object like {foo: {bar: 'qux'} }, the old foo will not get merged with the new foo, it will instead get replaced. So your updater function needs to do the deeper merging manually.
  • The updaterFunction looks like this ({info})=>({…}). We pull info out of the previous state, and we return an object, using info to manually do the deeper merging.

The benefit of passing a function to setState (instead of just an object) is that if you use the state passed to the function instead of this.state, you will avoid some potential bugs when multiple setState calls get batched together....