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
FormDataobject tofetchwill also force theContent-Typeof the request to bemultipart/form-datawhich is a very different kind of request thanapplication/x-www-form-urlencodingorapplication/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!