Easy React Forms with FormData
- tagged:
- react
- javascript
- programming
Storing each form input value in React state can be tedious, but it doesn't have to be that way.
Storing form state in React
I often see folks using state to store each individual value of a form:
export function ExampleForm() { const [username, setUsername] = React.useState('') const [password, setPassword] = React.useState('') const [email, setEmail] = React.useState('') return ( <form onSubmit={ev => { ev.preventDefault() const body = { username, password, email } fetch('/register', { method: 'post', body: JSON.stringify(body) }) }} > <div> <label> <span>username:</span> <input value={username} onChange={ev => setUsername(ev.target.value)} /> </label> </div> <div> <label> <span>password:</span> <input value={password} onChange={ev => setPassword(ev.target.value)} /> </label> </div> <div> <label> <span>email:</span> <input value={email} onChange={ev => setEmail(ev.target.value)} /> </label> </div> </form> )}
This works fine, but it's tedious to add event handlers to every form input, and if you later decide to add another form input, you'll be adding another state hook, and that's without mentioning the extra work you're asking React to do on every keystroke..
All of this, just because you want to grab values from a form on submission?
Fortunately there's a simpler way!
FormData API to the rescue
The FormData
api has been around for a while, but it doesn't seem to get used much.
When given a form element, it returns a FormData
object of all the inputs with a name
attribute. The input must have a name
attribute or it not be available.
In a React form we can pass the form element to FormData
by accessing it from the event given to the callback:
<form onSubmit={ev => { console.log(new FormData(ev.currentTarget))}}>
This returns a FormData
object, not a plain ol JavaScript object.
Passing a
FormData
object tofetch
will also force theContent-Type
of the request to bemultipart/form-data
which is a very different kind of request thanapplication/x-www-form-urlencoding
orapplication/json
.
If you have files to upload with your form, then this is ideal. You can pass the FormData
object to your fetch
request and be on your way:
<form onSubmit={ev => { ev.preventDefault() const body = new FormData(ev.currentTarget) fetch('/update-profile', { method: 'post', body })}}>
Again, your form inputs must have a name
attribute in order to be picked up by FormData
.
If we don't have files to upload and we want to send a application/json
request, we can do the following:
<form onSubmit={ev => { ev.preventDefault() const body = Object.fromEntries(new FormData(ev.currentTarget)) fetch('/new-post', { method: 'post', body: JSON.stringify(body) })}}>
Or if we want to send a application/x-www-form-urlencoding
request, we can use create a URLSearchParams
object:
<form onSubmit={ev => { ev.preventDefault() const body = new URLSearchParams(new FormData(ev.currentTarget)) fetch('/search', { method: 'get', body })}}>
Here's full working examples of both so you can compare:
with FormData api
with controlled inputs
as diff
export function ExampleForm() { return ( <form onSubmit={ev => { ev.preventDefault() const body = Object.fromEntries(new FormData(ev.currentTarget)) fetch('/register', { method: 'post', body: JSON.stringify(body) }) }} > <div> <label> <span>username:</span> <input name="username" /> </label> </div> <div> <label> <span>password:</span> <input name="password" /> </label> </div> <div> <label> <span>email:</span> <input name="email" /> </label> </div> </form> )}
Reset state
Sometimes you need to reset the form after submission. What do you do when you don't control the inputs with state?
useRef
!
export function ExampleForm() { const formRef = useRef(null) return ( <form ref={formRef} onSubmit={ev => { ev.preventDefault() const body = Object.fromEntries(new FormData(ev.currentTarget)) fetch('/new-post', { method: 'post', body: JSON.stringify(body) }).then(res => { if (res.ok) { formRef.current?.reset() } }) }} >
Validation
"What about validation", you ask?
There's a lot you can do with HTML attributes like required
, pattern
, minlength
, maxlength
, and there's a lot you can do before submission without controlled inputs, but there are some things that you'll want controlled inputs for. Fortunately it doesn't have to be an all-or-nothing thing, it's possible to have the majority of your inputs uncontrolled and only sprinkle state around when necessary.
Here's an example of only using state to show validation errors:
Keep in mind that validation in the browser is purely for the user experience, and is not a replacement for server side validation.
I hope this makes working with forms in React easier for you!