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

  1. Every field needs a name. Fields without one are skipped.
  2. new FormData(form) reads input, textarea, and select values, including files.
  3. fd.get('email') returns one value: a string, a File, or null if the field is missing.
  4. Object.fromEntries(fd.entries()) turns it into a plain object.
  5. fd.getAll('skills') returns every value for a name: a <select multiple> or a group of checkboxes sharing a name. get() and Object.fromEntries() keep only one of them.
  6. 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

React
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 a FormControl<string[]> gives an array.
  • A checkbox group is a FormGroup of booleans (or a FormArray), filtered on submit.
Angular
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:

Angular
// 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() returns FormDataEntryValue | 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 use getAll() for multi-value fields.
  • Don't set Content-Type yourself when sending FormData; the browser adds the multipart boundary.

Quick reference

TaskReact (FormData)Angular
All valuesObject.fromEntries(fd.entries())form.getRawValue()
One valuefd.get('email')form.controls.email.value
Multi-selectfd.getAll('skills')FormControl<string[]>
Checkbox groupfd.getAll('acquisition')FormGroup of booleans / FormArray
File uploadFormDataFormData in a service