Uncontrolled form inputs in React

Published on in JavaScript and React

Last updated on

If you need form input values only at the time of form submission, uncontrolled inputs can be nice to work with.

From Uncontrolled Components on React docs:

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.

I previously thought that using uncontrolled inputs in React is very rare or even a bad practice.

But then I found this tweet by Josh W. Comeau:

🌠 I've been really enjoying using uncontrolled form inputs recently. Set an initial value with React, and then collect the values using standard JS when the form is submitted.

No fussing with state required 🔥

CodeSandbox demo

function EditUser({ user }) {
  function saveUser(ev) {
    ev.preventDefault()

    const elementsArray = [...ev.target.elements]
    const formData = elementsArray.reduce((acc, elem) => {
      if (elem.id) acc[elem.id] = elem.value
      return acc
    }, {})

    // Do a `fetch` or whatever with `formData`!
    // Has the shape: `{ name: string, email: string }`
  }

  return (
    <form onSubmit={saveUser}>
      <label>
        Name
        <input type="text" id="name" defaultValue={user.name} />
      </label>
      <button>Save</button>
    </form>
  )
}

It makes sense to use uncontrolled form inputs if the input values are needed only at the time of form submission. I'd imagine the performance must be better as well.

From the replies on Twitter: creating a FormData object simplifies the saveUser function even more:

 function saveUser(ev) {
   ev.preventDefault()

-  const elementsArray = [...ev.target.elements]
-  const formData = elementsArray.reduce((acc, elem) => {
-    if (elem.id) acc[elem.id] = elem.value
-    return acc
-  }, {})
+  const formData = new FormData(ev.target)

   // Do a `fetch` or whatever with `formData`!
 }

Note that form inputs need to have a name prop/attribute or they won't end up in the formData object (if using new FormData(ev.target)).

Usage of the formData object:

function saveUser(ev) {
  ev.preventDefault()

  const formData = new FormData(ev.target)

  // `Content-Type` will implicitly be `multipart/form-data`
  fetch(url, {
    body: formData,
    method: 'POST',
  })

  // or

  const pojo = Object.fromEntries(formData)
  const json = JSON.stringify(pojo)
  fetch(url, {
    body: json,
    headers: { 'Content-Type': 'application/json' },
    method: 'POST',
  })
}

Demo on flems.io