Accessing state of an uncontrolled form
Tags: webdev, javascript, react, form
Published: 2023-01-29 17:37:10

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](https://thomas.hareau.eu/blog/acceder-etat-formulaire-non-controle.html) 

## Controlled form

For example, let's take this form. We have four inputs, "diet" being disabled if the "presence" checkbox is not ticked.  

{% codepen https://codepen.io/ThHareau/pen/MWBBoKj default-tab=js,result %}

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. 

```js
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:

1. we have way logic (and thus fewer bugs). We don't need form values before the submit action, so why storing them?
2. `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.

```js
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: 

```js
<div>
  <label for="diet">Diet : </label> 
  <input
    id="diet"
    name="diet"
    disabled={formData.get("presence") !== "on"}
  />
</div>
```

Which gives this final result:

{% codepen https://codepen.io/ThHareau/pen/vYaaJaX default-tab=js,result %}