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
This question is related to
javascript
node.js
reactjs
react-state
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: ''
}
}
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.
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 || ''}/>
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.
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)}/>
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
}
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.
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>
}
}
Simple solution to resolve this problem is to set an empty value by default :
<input name='myInput' value={this.state.myInput || ''} onChange={this.handleChange} />
Set a value to 'name' property in initial state.
this.state={ name:''};
_x000D_
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
An update for this. For React Hooks use const [name, setName] = useState(" ")
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 : ""}>
Source: Stackoverflow.com