610
votes

Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.*

Following is my code:

constructor(props) {
  super(props);
  this.state = {
    fields: {},
    errors: {}
  }
  this.onSubmit = this.onSubmit.bind(this);
}

....

onChange(field, e){
  let fields = this.state.fields;
  fields[field] = e.target.value;
  this.setState({fields});
}

....

render() {
  return(
    <div className="form-group">
      <input
        value={this.state.fields["name"]}
        onChange={this.onChange.bind(this, "name")}
        className="form-control"
        type="text"
        refs="name"
        placeholder="Name *"
      />
      <span style={{color: "red"}}>{this.state.errors["name"]}</span>
    </div>
  )
}
18
what is the initial value of fields in state?Mayank Shukla
constructor(props) { super(props); this.state = { fields: {}, errors: {} } this.onSubmit = this.onSubmit.bind(this); }Riya Kapuria

18 Answers

1134
votes

The reason is, in state you defined:

this.state = { fields: {} }

fields as a blank object, so during the first rendering this.state.fields.name will be undefined, and the input field will get its value as:

value={undefined}

Because of that, the input field will become uncontrolled.

Once you enter any value in input, fields in state gets changed to:

this.state = { fields: {name: 'xyz'} }

And at that time the input field gets converted into a controlled component; that's why you are getting the error:

A component is changing an uncontrolled input of type text to be controlled.

Possible Solutions:

1- Define the fields in state as:

this.state = { fields: {name: ''} }

2- Or define the value property by using Short-circuit evaluation like this:

value={this.state.fields.name || ''}   // (undefined || '') = ''
80
votes

Changing value to defaultValue will resolve it.

Note:

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. Read this for more.

I used value={this.state.input ||""} in input to get rid of that warning.

34
votes

Inside the component put the input box in the following way.

<input className="class-name"
              type= "text"
              id="id-123"
              value={ this.state.value || "" }
              name="field-name"
              placeholder="Enter Name"
              />
26
votes

SIMPLY, You must set initial state first

If you don't set initial state react will treat that as an uncontrolled component

26
votes

In addition to the accepted answer, if you're using an input of type checkbox or radio, I've found I need to null/undefined check the checked attribute as well.

<input
  id={myId}
  name={myName}
  type="checkbox" // or "radio"
  value={myStateValue || ''}
  checked={someBoolean ? someBoolean : false}
  />

And if you're using TS (or Babel), you could use nullish coalescing instead of the logical OR operator:

value={myStateValue ?? ''}
checked={someBoolean ?? false}
10
votes

Set Current State first ...this.state

Its because when you are going to assign a new state it may be undefined. so it will be fixed by setting state extracting current state also

this.setState({...this.state, field})

If there is an object in your state, you should set state as follows, suppose you have to set username inside the user object.

this.setState({user:{...this.state.user, ['username']: username}})
9
votes
const [name, setName] = useState()

generates error as soon as you type in the text field

const [name, setName] = useState('') // <-- by putting in quotes 

will fix the issue on this string example.

2
votes

If you use multiple input in on field, follow: For example:

class AddUser extends React.Component {
   constructor(props){
     super(props);

     this.state = {
       fields: { UserName: '', Password: '' }
     };
   }

   onChangeField = event => {
    let name = event.target.name;
    let value = event.target.value;
    this.setState(prevState => {
        prevState.fields[name] =  value;
        return {
           fields: prevState.fields
        };
    });
  };

  render() { 
     const { UserName, Password } = this.state.fields;
     return (
         <form>
             <div>
                 <label htmlFor="UserName">UserName</label>
                 <input type="text" 
                        id='UserName' 
                        name='UserName'
                        value={UserName}
                        onChange={this.onChangeField}/>
              </div>
              <div>
                  <label htmlFor="Password">Password</label>
                  <input type="password" 
                         id='Password' 
                         name='Password'
                         value={Password}
                         onChange={this.onChangeField}/>
              </div>
         </form>
     ); 
  }
}

Search your problem at:

onChangeField = event => {
    let name = event.target.name;
    let value = event.target.value;
    this.setState(prevState => {
        prevState.fields[name] =  value;
        return {
            fields: prevState.fields
        };
    });
};
2
votes

Using React Hooks also don't forget to set the initial value.
I was using <input type='datetime-local' value={eventStart} /> and initial eventStart was like

const [eventStart, setEventStart] = useState();
instead
const [eventStart, setEventStart] = useState('');.

The empty string in parentheses is difference.
Also, if you reset form after submit like i do, again you need to set it to empty string, not just to empty parentheses.

This is just my small contribution to this topic, maybe it will help someone.

2
votes

Put empty value if the value does not exist or null.

value={ this.state.value || "" }
1
votes

In my case it was pretty much what Mayank Shukla's top answer says. The only detail was that my state was lacking completely the property I was defining.

For example, if you have this state:

state = {
    "a" : "A",
    "b" : "B",
}

If you're expanding your code, you might want to add a new prop so, someplace else in your code you might create a new property c whose value is not only undefined on the component's state but the property itself is undefined.

To solve this just make sure to add c into your state and give it a proper initial value.

e.g.,

state = {
    "a" : "A",
    "b" : "B",
    "c" : "C", // added and initialized property!
}

Hope I was able to explain my edge case.

1
votes

The reason of this problem when input field value is undefined then throw the warning from react. If you create one changeHandler for multiple input field and you want to change state with changeHandler then you need to assign previous value using by spread operator. As like my code here.

constructor(props){
    super(props)
    this.state = {
        user:{
            email:'',
            password:''
        }
    }
}

// This handler work for every input field
changeHandler = event=>{
    // Dynamically Update State when change input value
    this.setState({
        user:{
            ...this.state.user,
            [event.target.name]:event.target.value
        }
    })
}

submitHandler = event=>{
    event.preventDefault()

    // Your Code Here...
}

render(){
    return (
        <div className="mt-5">
       
            <form onSubmit={this.submitHandler}>
                <input type="text" value={this.state.user.email} name="email" onChage={this.changeHandler} />
                
                <input type="password" value={this.state.user.password} name="password" onChage={this.changeHandler} />

                <button type="submit">Login</button>
            </form>
      

        </div>
    )
}
0
votes

Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.

Solution : Check if value is not undefined

React / Formik / Bootstrap / TypeScript

example :

{ values?.purchaseObligation.remainingYear ?
  <Input
   tag={Field}
   name="purchaseObligation.remainingYear"
   type="text"
   component="input"
  /> : null
}
0
votes

Multiple Approch can be applied:

  • Class Based Approch: use local state and define existing field with default value:
constructor(props) {
    super(props);
    this.state = {
      value:''
    }
  }
<input type='text'
                  name='firstName'
                  value={this.state.value}
                  className="col-12"
                  onChange={this.onChange}
                  placeholder='Enter First name' />

  • Using Hooks React > 16.8 in functional style components:
[value, setValue] = useState('');
<input type='text'
                  name='firstName'
                  value={value}
                  className="col-12"
                  onChange={this.onChange}
                  placeholder='Enter First name' />

  • If Using propTypes and providing Default Value for propTypes in case of HOC component in functional style.
 HOC.propTypes = {
    value       : PropTypes.string
  }
  HOC.efaultProps = {
    value: ''
  }

function HOC (){

  return (<input type='text'
                  name='firstName'
                  value={this.props.value}
                  className="col-12"
                  onChange={this.onChange}
                  placeholder='Enter First name' />)

}


0
votes

Change this

  const [values, setValues] = useState({intialStateValues});

for this

  const [values, setValues] = useState(intialStateValues);
0
votes

like this

value={this.state.fields && this.state.fields["name"] || ''}

work for me.

But I set initial state like this:

this.state = {
  fields: [],
}
0
votes

I came across the same warning using react hooks, Although I had already initialized the initial state before as:-

const [post,setPost] = useState({title:"",body:""})

But later I was overriding a part of the predefined state object on the onChange event handler,

 const onChange=(e)=>{
        setPost({[e.target.name]:e.target.value})
    }

Solution I solved this by coping first the whole object of the previous state(by using spread operators) then editing on top of it,

 const onChange=(e)=>{
        setPost({...post,[e.target.name]:e.target.value})
    }
0
votes

As mentioned above you need to set the initial state, in my case I forgot to add ' ' quotes inside setSate();

  const AddUser = (props) => {
  const [enteredUsername, setEnteredUsername] = useState()
  const [enteredAge, setEnteredAge] = useState()

Gives the following error

enter image description here

Correct code is to simply set the initial state to an empty string ' '

  const AddUser = (props) => {
  const [enteredUsername, setEnteredUsername] = useState('')
  const [enteredAge, setEnteredAge] = useState('')