ChatGPT解决这个技术问题 Extra ChatGPT

React - changing an uncontrolled input

I have a simple react component with the form which I believe to have one controlled input:

import React from 'react';

export default class MyForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {}
    }

    render() {
        return (
            <form className="add-support-staff-form">
                <input name="name" type="text" value={this.state.name} onChange={this.onFieldChange('name').bind(this)}/>
            </form>
        )
    }

    onFieldChange(fieldName) {
        return function (event) {
            this.setState({[fieldName]: event.target.value});
        }
    }
}

export default MyForm;

When I run my application I get the following warning:

Warning: MyForm 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

I believe my input is controlled since it has a value. I am wondering what am I doing wrong?

I am using React 15.1.0


f
fvgs

I believe my input is controlled since it has a value.

For an input to be controlled, its value must correspond to that of a state variable.

That condition is not initially met in your example because this.state.name is not initially set. Therefore, the input is initially uncontrolled. Once the onChange handler is triggered for the first time, this.state.name gets set. At that point, the above condition is satisfied and the input is considered to be controlled. This transition from uncontrolled to controlled produces the error seen above.

By initializing this.state.name in the constructor:

e.g.

this.state = { name: '' };

the input will be controlled from the start, fixing the issue. See React Controlled Components for more examples.

Unrelated to this error, you should only have one default export. Your code above has two.


It is difficult to read answer and follow the idea many times but this answer is the perfect way of story telling and making viewer understand at same time. Answer level god!
What if you have dynamic fields in a loop? e.g. you set the field name to be name={'question_groups.${questionItem.id}'}?
How would dynamic fields work. Am generating the form dynamically and then set the field names in an object inside state, do i still have to first manually set the field names in state first and then transfer them to my object?
This answer is slightly incorrect. An input is controlled if the value prop has a non-null/undefined value. The prop doesn't need to correspond to a state variable (it could just be a constant and the component would still be considered controlled). Adam's answer is more correct and should be accepted.
Yeah - this is technically the wrong answer (but helpful). A note on this - if you are dealing with radio buttons (whose 'value' is controlled a bit differently), this react warning can actually throw even if your component is controlled (currently). github.com/facebook/react/issues/6779, and can be fixed by adding a !! to the truthiness of isChecked
M
Mohamad Shiralizadeh

When you first render your component, this.state.name isn't set, so it evaluates to undefined or null, and you end up passing value={undefined} or value={null}to your input.

When ReactDOM checks to see if a field is controlled, it checks to see if value != null (note that it's !=, not !==), and since undefined == null in JavaScript, it decides that it's uncontrolled.

So, when onFieldChange() is called, this.state.name is set to a string value, your input goes from being uncontrolled to being controlled.

If you do this.state = {name: ''} in your constructor, because '' != null, your input will have a value the whole time, and that message will go away.


For what it's worth, the same thing can happen with this.props.<whatever>, which was the problem on my end. Thanks, Adam!
Yep! This could also happen if you pass a calculated variable that you define in render(), or an expression in the tag itself—anything that evaluates to undefined. Glad I could help!
Thanks for this answer: even though it's not the accepted one it explains the issue far better than just "specify a name".
I've updated my answer to explain how the controlled input works. It's worth noting that what matters here is not that a value of undefined is being passed initially. Rather, it's the fact that this.state.name does not exist as a state variable that makes the input uncontrolled. For example, having this.state = { name: undefined }; would result in the input being controlled. It should be understood that what matters is where the value comes from, not what the value is.
@fvgs having this.state = { name: undefined } would still result in an uncontrolled input. <input value={this.state.name} /> desugars to React.createElement('input', {value: this.state.name}). Because accessing a nonexistent property of an object returns undefined, this evaluates to the exact same function call—React.createElement('input', {value: undefined})—whether name is not set or explicitly set to undefined, so React behaves in the same way. You can see this behaviour in this JSFiddle.
J
JpCrow

Another approach it could be setting the default value inside your input, like this:

 <input name="name" type="text" value={this.state.name || ''} onChange={this.onFieldChange('name').bind(this)}/>

I think this would also work
Thanks a lot, this was exactly was I was looking for. Are there any disadvantages or potential problems using this pattern I should keep in mind?
This worked for me, as fields are dynamically rendered from the API so I don't know the name of the field when the component is mounted. This worked a treat!
G
Geo

I know others have answered this already. But a very important factor here that may help other people experiencing similar issue:

You must have an onChange handler added in your input field (e.g. textField, checkbox, radio, etc). Always handle activity through the onChange handler.

Example:

<input ... onChange={ this.myChangeHandler} ... />

When you are working with checkbox you may need to handle its checked state with !!.

Example:

<input type="checkbox" checked={!!this.state.someValue} onChange={.....} >

Reference: https://github.com/facebook/react/issues/6779#issuecomment-326314716


this works for me, thanks, yeah, I am 100% percent agree with that, initial state is {}, so checked value will be undefined and make it uncontrolled,
Double bang solution works just fine, thanks!
This helped me. Thank you.
what if we are fetching data from an API and displaying it in a TextField?
M
Mustapha GHLISSI

Simple solution to resolve this problem is to set an empty value by default :

<input name='myInput' value={this.state.myInput || ''} onChange={this.handleChange} />

With pleasure :)
G
Greg R Taylor

One potential downside with setting the field value to "" (empty string) in the constructor is if the field is an optional field and is left unedited. Unless you do some massaging before posting your form, the field will be persisted to your data storage as an empty string instead of NULL.

This alternative will avoid empty strings:

constructor(props) {
    super(props);
    this.state = {
        name: null
    }
}

... 

<input name="name" type="text" value={this.state.name || ''}/>

M
Menelaos Kotsollaris

In my case, I was missing something really trivial.

<input value={state.myObject.inputValue} />

My state was the following when I was getting the warning:

state = {
   myObject: undefined
}

By alternating my state to reference the input of my value, my issue was solved:

state = {
   myObject: {
      inputValue: ''
   }
}

Thank you for helping me understand the real problem I was having
K
KARTHIKEYAN.A

When you use onChange={this.onFieldChange('name').bind(this)} in your input you must declare your state empty string as a value of property field.

incorrect way:

this.state ={
       fields: {},
       errors: {},
       disabled : false
    }

correct way:

this.state ={
       fields: {
         name:'',
         email: '',
         message: ''
       },
       errors: {},
       disabled : false
    }

E
Emperor Krauser

If the props on your component was passed as a state, put a default value for your input tags

<input type="text" placeholder={object.property} value={object.property ? object.property : ""}>

l
lakmal_sathyajith

Set a value to 'name' property in initial state.

this.state={ name:''};


k
kboul

An update for this. For React Hooks use const [name, setName] = useState(" ")


Thanks 4 the update Jordan, this error is a bit hard to solve
Why " " and not ""? This causes you to lose any hint text, and if you click and type you get a sneaky space before any data you enter.
R
RobKohr

Simply create a fallback to '' if the this.state.name is null.

<input name="name" type="text" value={this.state.name || ''} onChange={this.onFieldChange('name').bind(this)}/>

This also works with the useState variables.


This was the best option for me as I purposefully have undefined as the value in my state. Can someone who downvoted please explain why? A bit down the page there is the same response as this one but with 55+ upvotes.
A
Alec Mingione

I believe my input is controlled since it has a value.

Now you can do this two ways the best way is to have a state key to each input with 1 onChange handler. If you have checkboxes you will need to write a separate onChange handler.

With a Class component you would want to write it like this 👇


import React from 'react';

export default class MyForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
         myFormFields: {
           name: '',
           dob: '',
           phone: ''
         }
        }

      this.onFormFieldChange = this.onFormFieldChange.bind(this)
    }


// Always have your functions before your render to keep state batches in sync.
    onFormFieldChange(e) {
       // No need to return this function can be void
       this.setState({
         myFormFields: {
            ...this.state.myFormFields,
            [e.target.name]: e.target.value 
         }
       })
    }


    render() {
       // Beauty of classes we can destruct our state making it easier to place
       const { myFormFields } = this.state
      
        return (
            <form className="add-support-staff-form">
                <input name="name" type="text" value={myFormFields.name} onChange={this.onFormFieldChange}/>
                <input name="dob" type="date" value={myFormFields.dob} onChange={this.onFormFieldChange}/>
                <input name="phone" type="number" value={myFormFields.phone} onChange={this.onFormFieldChange}/>
            </form>
        )
    }

}

export default MyForm;

Hope that helps for a class but the most performative and what the newest thing the devs are pushing everyone to use is Functional Components. This is what you would want to steer to as class components don't intertwine well with the latest libraries as they all use custom hooks now.

To write as a Functional Component


import React, { useState } from 'react';

const MyForm = (props) => {
    // Create form initial state
    const [myFormFields, setFormFields] = useState({
       name: '',
       dob: '',
       phone: ''
    })


   // Always have your functions before your return to keep state batches in sync.
    const onFormFieldChange = (e) => {
       // No need to return this function can be void
       setFormFields({
         ...myFormFields,
         [e.target.name]: e.target.value 
       })
    }

      
       return (
            <form className="add-support-staff-form">
                <input name="name" type="text" value={myFormFields.name} onChange={onFormFieldChange}/>
                <input name="dob" type="date" value={myFormFields.dob} onChange={onFormFieldChange}/>
                <input name="phone" type="number" value={myFormFields.phone} onChange={onFormFieldChange}/>
            </form>
        )

}

export default MyForm;

Hope this helps! 😎


Excellent buddy
M
Marecky

In my case component was rerendering and throwing A component is changing an uncontrolled input of type checkbox to be controlled error. It turned out that this behaviour was a result of not keeping true or false for checkbox checked state (sometimes I got undefined). Here what my faulty component looked like:

import * as React from 'react';
import { WrappedFieldProps } from 'redux-form/lib/Field';

type Option = {
  value: string;
  label: string;
};

type CheckboxGroupProps = {
  name: string;
  options: Option[];
} & WrappedFieldProps;


const CheckboxGroup: React.FC<CheckboxGroupProps> = (props) => {

  const {
    name,
    input,
    options,
  } = props;

  const [value, setValue] = React.useState<string>();
  const [checked, setChecked] = React.useState<{ [name: string]: boolean }>(
    () => options.reduce((accu, option) => {
      accu[option.value] = false;
      return accu;
    }, {}),
  );

  React.useEffect(() => {
    input.onChange(value);

    if (value) {
      setChecked({
        [value]: true, // that setChecked argument is wrong, causes error
      });
    } else {
      setChecked(() => options.reduce((accu, option) => {
        accu[option.value] = false;
        return accu;
      }, {}));
    }

  }, [value]);

  return (
    <>
      {options.map(({ value, label }, index) => {
        return (
          <LabeledContainer
            key={`${value}${index}`}
          >
            <Checkbox
              name={`${name}[${index}]`}
              checked={checked[value]}
              value={value}
              onChange={(event) => {
                if (event.target.checked) {
                  setValue(value);
                } else {
                  setValue(undefined);
                }
                return true;
              }}
            />
            {label}
          </LabeledContainer>
        );
      })}
    </>
  );
};

To fix that problem I changed useEffect to this

React.useEffect(() => {
  input.onChange(value);

  setChecked(() => options.reduce((accu, option) => {
      accu[option.value] = option.value === value;
      return accu;
    }, {}));

}, [value]);

That made all checkboxes keep their state as true or false without falling into undefined which switches control from React to developer and vice versa.


G
Guillaume Munsch

For people using Formik, you need to add a default value for the specific field name to the form's initialValues.


You'd better provide an example. It's hard to understand.
@DavidPiao it makes perfect sense to me, can you elaborate?
S
Shuhad zaman

I had the same problem. the problem was when i kept the state info blank

  const [name, setName] = useState()

I fixed it by adding empty string like this

  const [name, setName] = useState('')

Thank you sir! this was my problem and fix
A
Ashish Singh

This generally happens only when you are not controlling the value of the filed when the application started and after some event or some function fired or the state changed, you are now trying to control the value in input field.

This transition of not having control over the input and then having control over it is what causes the issue to happen in the first place.

The best way to avoid this is by declaring some value for the input in the constructor of the component. So that the input element has value from the start of the application.


T
Tikam Chand

Please try this code

import React from "react";

class MyForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = { name: "" };
        this.onFieldChange = this.onFieldChange.bind(this);
    }

    onFieldChange(e) {
        this.setState({[e.target.name]: e.target.value});
    }

    render() {
        return (
            <form className="add-support-staff-form">
                <input name="name" type="text" value={this.state.name} onChange={this.onFieldChange} />
        </form>
        );
    }
}

export default MyForm;

A
Abdulrazak Zakieh

In short, if you are using class component you have to initialize the input using state, like this:

this.state = { the_name_attribute_of_the_input: "initial_value_or_empty_value" };

and you have to do this for all of your inputs you'd like to change their values in code.

In the case of using functional components, you will be using hooks to manage the input value, and you have to put initial value for each input you'd like to manipulate later like this:

const [name, setName] = React.useState({name: 'initialValue'});

If you'd like to have no initial value, you can put an empty string.


M
Mark

For dynamically setting state properties for form inputs and keeping them controlled you could do something like this:

const inputs = [
    { name: 'email', type: 'email', placeholder: "Enter your email"},
    { name: 'password', type: 'password', placeholder: "Enter your password"},
    { name: 'passwordConfirm', type: 'password', placeholder: "Confirm your password"},
]

class Form extends Component {
  constructor(props){
    super(props)
    this.state = {} // Notice no explicit state is set in the constructor
  }

  handleChange = (e) => {
    const { name, value } = e.target;

    this.setState({
      [name]: value
    }
  }

  handleSubmit = (e) => {
    // do something
  }

  render() {
     <form onSubmit={(e) => handleSubmit(e)}>
       { inputs.length ?
         inputs.map(input => {
           const { name, placeholder, type } = input;
           const value = this.state[name] || ''; // Does it exist? If so use it, if not use an empty string

           return <input key={name}  type={type} name={name} placeholder={placeholder} value={value} onChange={this.handleChange}/>
       }) :
         null
       }
       <button type="submit" onClick={(e) => e.preventDefault }>Submit</button>
     </form>    
  }
}