Working with Forms & User InputEntry 4 of 10
Reading Values with FormData
FormData is a browser API (not React) that collects every named field of a <form> into key-value pairs.
react.dev<form>
Notes
- Every field needs a
name. Fields without one are skipped. new FormData(form)readsinput,textarea, andselectvalues, including files.fd.get('email')returns one value: astring, aFile, ornullif the field is missing.Object.fromEntries(fd.entries())turns it into a plain object.fd.getAll('skills')returns every value for a name: a<select multiple>or a group of checkboxes sharing a name.get()andObject.fromEntries()keep only one of them.- Unchecked checkboxes are not included at all.
To keep multi-value fields, build the object first, then overwrite those keys with getAll().
What / Why / When
- What
Gathering all form values in one call.
- Why
No state or ref per input; works for any number of fields.
- When
You only need the values on submit, including file uploads and dynamic fields.
- Use case
A signup form with a multi-select and an "acquisition channel" checkbox group.
Code compare
import type { FormEvent } from "react";​export default function Signup() { function handleSubmit(event: FormEvent<HTMLFormElement>) { event.preventDefault();​ const form = event.currentTarget; const fd = new FormData(form);​ const data = Object.fromEntries(fd.entries()) as Record< string, FormDataEntryValue | FormDataEntryValue[] >; data.acquisition = fd.getAll("acquisition"); // checkbox group: keep every checked value data.skills = fd.getAll("skills"); // <select multiple>​ console.log(data); form.reset(); // Entry 5 }​ return ( <form onSubmit={handleSubmit}> <input name="email" type="email" placeholder="Email" /> <input name="password" type="password" placeholder="Password" />​ <select name="skills" multiple> <option value="react">React</option> <option value="angular">Angular</option> <option value="vue">Vue</option> </select>​ <fieldset> <legend>How did you find us?</legend> <label> <input type="checkbox" name="acquisition" value="google" /> Google </label> <label> <input type="checkbox" name="acquisition" value="friend" /> Referred by friend </label> <label> <input type="checkbox" name="acquisition" value="other" /> Other </label> </fieldset>​ <button type="reset">Reset</button> <button type="submit">Sign up</button> </form> );}Angular's form model already holds structured values, so FormData is only needed for file uploads. Multi-value fields map to arrays:
- A
<select multiple>bound to aFormControl<string[]>gives an array. - A checkbox group is a
FormGroupof booleans (or aFormArray), filtered on submit.
import { Component, inject } from "@angular/core";import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms";import { SignupService } from "./signup.service";​@Component({ selector: "app-signup", imports: [ReactiveFormsModule], template: ` <form [formGroup]="form" (ngSubmit)="onSubmit()"> <input formControlName="email" type="email" placeholder="Email" /> <input formControlName="password" type="password" placeholder="Password" />​ <select formControlName="skills" multiple> <option value="react">React</option> <option value="angular">Angular</option> <option value="vue">Vue</option> </select>​ <fieldset formGroupName="acquisition"> <legend>How did you find us?</legend> <label><input type="checkbox" formControlName="google" /> Google</label> <label><input type="checkbox" formControlName="friend" /> Referred by friend</label> <label><input type="checkbox" formControlName="other" /> Other</label> </fieldset>​ <button type="reset">Reset</button> <button type="submit">Sign up</button> </form> `,})export class SignupComponent { private signupService = inject(SignupService);​ form = new FormGroup({ email: new FormControl("", { nonNullable: true }), password: new FormControl("", { nonNullable: true }), skills: new FormControl<string[]>([], { nonNullable: true }), acquisition: new FormGroup({ google: new FormControl(false, { nonNullable: true }), friend: new FormControl(false, { nonNullable: true }), other: new FormControl(false, { nonNullable: true }), }), });​ onSubmit() { const { acquisition, ...rest } = this.form.getRawValue(); const channels = Object.entries(acquisition) .filter(([, checked]) => checked) .map(([key]) => key);​ this.signupService .signup({ ...rest, acquisition: channels }) .subscribe(() => this.form.reset()); }}File uploads use FormData in both frameworks:
// upload.service.tsupload(file: File) { const fd = new FormData(); fd.append('file', file); return this.http.post(`${environment.apiUrl}/uploads`, fd); // browser sets the multipart header}Differences and gotchas
- In TypeScript,
fd.get()returnsFormDataEntryValue | null(string | File | null). Narrow it before using it as a string:String(fd.get('email') ?? ''). Object.fromEntries()keeps only the last value of a repeated name. Always usegetAll()for multi-value fields.- Don't set
Content-Typeyourself when sendingFormData; the browser adds the multipart boundary.
Quick reference
| Task | React (FormData) | Angular |
|---|---|---|
| All values | Object.fromEntries(fd.entries()) | form.getRawValue() |
| One value | fd.get('email') | form.controls.email.value |
| Multi-select | fd.getAll('skills') | FormControl<string[]> |
| Checkbox group | fd.getAll('acquisition') | FormGroup of booleans / FormArray |
| File upload | FormData | FormData in a service |