We, as software developers, often build different kinds of forms, which consist of different elements, like <input />
, <textarea />
, <select />
, etc.
There are 2 ways of defining those elements, the controlled and uncontrolled way.
The element is controlled when its state is controlled by us, or to be more precise, by the React component.
The state becomes the "Single Source of Truth", which is something we should always strive for.
Important note: basically, it can be said that the element is controlled, when the value
property is provided. However, you should remember that providing value
property without onChange
handler leads to a warning:
Take a look at the following Example
component:
const Example = () => {
const [name, setName] = useState('');
const handleChange = (e: any) => {
setName(e.target.value);
};
return <input type="text" value={name} onChange={handleChange} />;
};
We store the user's input inside of the name
variable, which is updated every time the user types something inside of the input.
The name
variable can be printed below to see how it gets updated:
const Example = () => {
const [name, setName] = useState('');
const handleChange = (e: any) => {
setName(e.target.value);
};
return (
<>
<input type="text" value={name} onChange={handleChange} />
<div>Input: {name}</div>
</>
);
};
When the user types something in the input, the component is re-rendered.
The uncontrolled components act like traditional HTML elements.
The state of each component is stored inside of the DOM(Document Object Model), not in the component, which means that you don't have to take care of updating the state.
The only thing you should take care of is pulling out that state to your React component.
How can you do that?
By using a reference (ref
).
Refs provide a way to access DOM nodes or React elements created in the render method.
Let's see an example of getting the value using ref
:
const Example = () => {
const [name, setName] = useState('');
const ref = useRef(null);
const handleSubmit = e => {
e.preventDefault();
setName(ref.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={ref} />
<button>Submit</button>
<div>Input: {name}</div>
</form>
);
};
Note, that we have access to the input
element by using ref.current
and to its value by ref.current.value
.
Important note: uncontrolled components aren't re-rendered when the user types something, therefore they have a little bit better performance.
As it's officially stated in the React documentation, we should use controlled components as much as possible.
Then, why do we even have a possibility to define uncontrolled components?
They are useful if:
The answer to the question above is: "it depends".
Don't be afraid to make a mistake by making the wrong choice. Migrating components from one type to another isn't hard at all.