React/ReactJS: Difference between defaultValue and value
Forms are integral part of almost every web application; even the simplest of business websites has a contact form.
In React, forms can be implemented in two ways and here is where beginners often stumble upon the common mistake of assigning a value
without a handler or using both defaultValue
and value
together on form elements, which leads to logging warning messages (as such) in the console:
Warning: [YourComponent] contains an input of type text with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props.
In this tutorial, we will rectify such error-prone approaches and learn how to implement forms properly thereby exploring the concepts of controlled and uncontrolled components in React. But before we begin, here is the concise summary of the entire lesson:
In React, defaultValue
is used with uncontrolled form components whereas value
is used with controlled form components. They should not be used together in a form element.
defaultValue
and Uncontrolled Components
The traditional HTML forms where you use JavaScript/jQuery to get the values from its input elements are the typical forms with uncontrolled components. There is no explicit use of state variables and no implementation of any event handler whatsoever to update the input values. So, inserting onChange()
to the uncontrolled <input/>
component in this case will be unnecessary. Their values are just what we type in.
To go about implementing such uncontrolled components inside forms in React, we need to attach a ref to the <input/>
element. This ref is created inside the constructor()
via the React.createRef()
API. When the form is submitted, the reference to the <input/>
element is achieved via the current
attribute of the attached ref. So, the value which the user enters can be had with current.value
.
Here is an example of a form with an uncontrolled <input/>
component (which is of type=email
).
class NewsletterForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.email = React.createRef();
}
handleSubmit(e) {
console.log('Submitted email: ', this.email.current.value);
e.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Email:
<input type="email" ref={this.email} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
export default NewsletterForm;
We input some value which is fetched with this.email.current.value
inside handleSubmit()
. This is also the same value which document.querySelector('input[type=email]').value
would return.
Now what happens when we add the value
attribute to the <input/>
? Suppose we decide to give it an intial value of something, say, example@example.com
.
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Email:
<input value="example@example.com" type="email"
ref={this.email} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
Firstly, you will notice that the application threw a warning message into the browser's console.
Warning: Failed prop type: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.
Forget the warning message for the time being.
The other thing you will notice is that you are unable to type in or enter anything into the input box; whatever you are typing in is not showing or reflecting — the initially assigned value example@example.com
stays put! This is because, in the lifecycle of React's rendering, the value
attributes in the form elements overrides the values in the DOM. To handle such cases, React came up with the defaultValue
attribute, which specifies the initial value but leave the ensuing updates uncontrolled. Here is the correct way.
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Email:
<input defaultValue="example@example.com" type="email"
ref={this.email} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
You will also notice that the warning message is also not logged this time.
value
and Controlled Components
In controlled components, we assign a state variable to the value
along with a handler to update the assigned state variable. Below is an example. The value
attribute is assigned the this.state.email
state variable and the onChange
event is assigned the handleEmail()
handler, which gets triggered everytime a character is typed, which in turn updates the this.state.email
state and thereafter the displayed value. Remember again, defaultValue
and value
are never used together in a form element.
This is the recommended way to implement forms in React.
class NewsletterForm extends React.Component {
constructor(props) {
super(props);
this.state = {
email: ''
};
}
handleEmail = (e) => {
this.setState({
email: e.target.value
});
}
render() {
return (
<form>
<label>
Email:
<input type="email" value={this.state.email}
onChange={this.handleEmail}/>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
export default NewsletterForm;