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 🔥
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',
})
}