Controlled components, simply put, are form input elements that have their value mapped to the component state. This means that any change in the input element data will change the component state and vice versa.
From the React Documentation:
In most cases, we recommend using controlled components to implement forms. In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself.
For Example:
It isn’t the only way of managing the form data, the opposite of controlled components are uncontrolled components where you don’t map any state to form inputs. They work the same way traditional HTML forms work. Nothing react-y in them.
You can get the input values in uncontrolled components using React refs, which we’ll briefly discuss.
There are many practical use cases for using controlled components over uncontrolled ones. Although there can be many cases where it makes more sense to go with uncontrolled components like when migrating a non-react codebase to react, using inputs that don’t support controlled components.
The one where the form data is handled by the DOM itself and we use React Refs to get the data.
class MyUnControlledComponent extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.emailRef = React.createRef();
}
handleSubmit(event) {
console.log("The email address is: " + this.emailRef.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
Email:
<input type="text" ref={this.emailRef} />
<input type="submit" value="Submit" />
</form>
);
}
}
So now imagine if you have 10 fields in your form, you’d have to create 10 refs and bind each to their corresponding input elements. I think there should be a better approach than this.
A controlled component takes a ‘value’ prop and a callback ‘onChange’. The value passed to the ‘value’ prop is the state in that component or its parent component. ‘onChange’ prop takes a callback function which gets called every time the user makes any change to the input element and it updates the state on every change.
class MyControlledComponent extends React.Component{
constructor{
super(props)
this.state = {
email:"",
}
}
handleChange = (event)=>{
this.setState({
email: event.target.value
})
}
render(){
return(
<input type="text" value={this.state.email} onChange={this.handleChange} />
)
}
}
Default input value is binded from the state
-> User updates the input
-> onChange updates the state
-> input element shows the updated state
Everything happens in ‘real time’ so to speak. You’re not changing the input value, you’re re-rendering the input element based on its updated ‘value’ prop.
Here we’re using the useState hook to maintain the state in a functional component. Nothing fancy and completely optional.
function Form()=>{
const [email, setEmail]= React.useState("");
return(
<input type="text" value={email}
onChange={(event)=>{setEmail(event.target.value)}} />
)
}
function Form()=>{
const [checked, setChecked]= React.useState(false);
return(
<input type="checkbox" checked={checked} onChange={(event)=>{
setChecked(event.target.checked)
}} />
)
}
function Form()=>{
const [option, setOption]= React.useState("");
return(
<select value={option} onChange={(event)=>{setOption(event.target.value)}}>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
<option value="option4">Option 4</option>
</select>
)
}
Same as input type “text”.
function Form()=>{
const [longertext, setLongertext]= React.useState("");
return(
<textarea value={longertext}
onChange={(event)=>{setLongertext(event.target.value)}} />
)
}
file
input tagThe file input: The “file” input type will always be uncontrolled because its value can only be set by the user and not programmatically. Your program can’t know which file to choose and so can’t set its value on its own. You can handle input type file using React refs:
...
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.fileInput = React.createRef();
}
handleSubmit(event) {
event.preventDefault();
console.log("Selected file is: " + this.fileInput.current.files[0].name);
}
...
We’ve covered everything we need to know to implement controlled and uncontrolled components here. We can use this knowledge to implement complex use cases like: