Controlled forms are convenient because they give a thin control over the different values. However, they tend to be a bit verbose.
On the other side, uncontrolled forms are often shorter and more readable. They let the browser natively handle the component, reducing the needed boilerplate for controlled components.
Note: a French translation of this article exists on my website
Controlled form
For example, let's take this form. We have four inputs, "diet" being disabled if the "presence" checkbox is not ticked.
Here we need to handle all fields manually. This works, but seems to be unnecessary when HTML alone handles this perfectly well.
Uncontrolled form
Let's take the same example, using an uncontrolled form.
const action = (event) => {
event.preventDefault();
const data = new FormData(event.currentTarget);
console.log("Saving...", Object.fromEntries(data.entries()));
};
const App = () => {
return (
<div className="App">
<form onSubmit={action}>
<fieldset>
<legend>Identity</legend>
<div>
<label for="name">Name : </label>
<input id="name" name="name" defaultValue="Chris" />
</div>
<div>
<label for="email">Email : </label>
<input id="email" type="email" name="email" defaultValue="chris@email.test" />
</div>
</fieldset>
<fieldset>
<legend>Event</legend>
<div>
<label for="presence">Presence : </label>
<input id="presence" type="checkbox" name="presence" defaultChecked />
</div>
<div>
<label for="diet">Diet : </label>
<input id="diet" name="diet" />
</div>
</fieldset>
<button type="submit">Submit</button>
</form>
</div>
);
};
We can notice two main changes here:
- we have way logic (and thus fewer bugs). We don't need form values before the submit action, so why storing them?
-
action
reads value directly from the event. As it does not use React states, we can extract it outside the component, and split our code more easily.
Oh, and maybe a last point: we don't handle the disabled field anymore. Let's do it!
Accessing state using uncontrolled forms
We need to read the current value of the "presence" checkbox to disable the "diet" field. Using controlled components, it is easy. Here, less so.
A solution to this problem is to add an onChange
listener on the form itself. It will provide read-only access to the current values of the form.
const App = () => {
const [formData, setFormData] = useState(new FormData())
return (
<div className="App">
<form onSubmit={action} onChange={(event) => setFormData(new FormData(event.currentTarget))}>
And then, on the diet
field, we add:
<div>
<label for="diet">Diet : </label>
<input
id="diet"
name="diet"
disabled={formData.get("presence") !== "on"}
/>
</div>
Which gives this final result:
Top comments (2)
Hey, that was a nice read, you got my follow, keep writing 😉
Thank you very much @naubit