FormData + FormDataEvent + requestSubmit — React 19 <form action={fn}> #31

Closed
opened 2026-06-17 16:34:48 +00:00 by Artur · 0 comments
Owner

Problem

React 19 <form action={fn}> und <button formAction={fn}> nutzen die neue form action API:

  1. Beim Submit erzeugt der Browser automatisch ein FormData-Objekt aus den Formularfeldern
  2. Dispatched ein formdata-Event (FormDataEvent) auf dem Formular
  3. Ruft die asynchrone Action-Funktion mit dem FormData-Objekt auf
  4. requestSubmit() statt submit() wird verwendet, damit Submit-Events + FormData-Events dispatched werden

Unsere Engine:

  • FormData gibt es nicht
  • FormDataEvent gibt es nicht
  • HTMLFormElement.requestSubmit() gibt es nicht
  • form.submit() dispatht kein submit-Event (muss es laut Spec auch nicht)

Option A: Neuimplementierung (Empfohlen)

Alle drei Komponenten als eigenständige Module implementieren, mit TDD.

Modul 1: FormData (src/forms/form-data.ts)

export class FormData {
  private _entries: Array<[string, FormDataEntryValue]> = [];

  append(name: string, value: string | Blob, filename?: string): void;
  delete(name: string): void;
  get(name: string): FormDataEntryValue | null;
  getAll(name: string): FormDataEntryValue[];
  has(name: string): boolean;
  set(name: string, value: string | Blob, filename?: string): void;
  // Iteration
  entries(): Iterator<[string, FormDataEntryValue]>;
  keys(): Iterator<string>;
  values(): Iterator<FormDataEntryValue>;
  forEach(cb: (value, key, parent) => void): void;
  // Encodierung
  getContentType(): string; // multipart/form-data; boundary=...
  toBuffer(): ArrayBuffer; // encode boundary-separated body
}

Wichtig: FormData muss sowohl String- als auch Blob-Werte unterstützen (File-Upload).

Modul 2: FormDataEvent (src/forms/form-data-event.ts)

export class FormDataEvent extends Event {
  readonly formData: FormData;
  constructor(type: string, options?: { formData: FormData });
}

Modul 3: requestSubmit + submit-event-plumbing (src/forms/form-submission.ts)

// Auf HTMLFormElement.prototype.requestSubmit:
HTMLFormElement.prototype.requestSubmit = function(submitter?: HTMLElement) {
  // 1. Dispatch submit event
  const submitEvent = new SubmitEvent("submit", { cancelable: true, submitter });
  const cancelled = !this.dispatchEvent(submitEvent);
  if (cancelled) return;

  // 2. Collect form data
  const fd = new FormData(this, submitter); // submitter gibt ggf. name/value

  // 3. Dispatch formdata event (modifiable!)
  const fdEvent = new FormDataEvent("formdata", { formData: fd });
  this.dispatchEvent(fdEvent);

  // 4. If form.action is a function (React 19 pattern), call it
  const action = (this as any).action;
  if (typeof action === "function") {
    action(fd);
  } else {
    // Standard-HTTP-Submit (wenn action-URL angegeben)
    this._legacySubmit();
  }
};

Option B: FormData aus dem FormData-Standard-Extraktor

Es gibt ein formdata Polyfill auf npm — aber das deckt nur formdata Event ab, nicht die FormData-Klasse selbst.

Nicht empfohlen: FormData selbst muss alle Edge Cases der multipart-Enkodierung beherrschen.

Akzeptanzkriterien

  • FormData-Instanz erstellbar (leer und aus <form>-Element)
  • append/delete/get/getAll/has/set funktionieren spec-konform
  • String-Werte korrekt gespeichert und abrufbar
  • Blob-Werte korrekt gespeichert und abrufbar
  • FormDataEvent dispatcht mit formData-Property
  • form.requestSubmit(submitter?) → dispatcht submit → sammelt FormData → dispatcht formdata → ruft action-Funktion
  • form.requestSubmit() mit cancelable submit → preventDefault() bricht ab
  • form.requestSubmit() ohne action-Funktion → kein Crash
  • Alle bestehenden Tests bleiben grün

Betroffene Dateien

Datei Änderung
src/forms/form-data.ts Neu: FormData-Klasse mit multipart-Encoding
src/forms/form-data-event.ts Neu: FormDataEvent extends Event
src/forms/form-submission.ts Neu: requestSubmit, form-action-handling
src/runtime-isolation.ts FormData + FormDataEvent in allowedGlobals registrieren
tests/unit/form-data.test.ts Neu: 15+ Tests
tests/unit/form-submission.test.ts Neu: 8+ Tests
tests/integration/react-form.test.ts Neu: Integrationstest mit React-Formular-Snippet

Tests (FormData)

describe("FormData", () => {
  it("leere FormData hat size 0");
  it("append fügt String-Wert hinzu");
  it("get gibt ersten Wert für key zurück");
  it("get für unbekannten key gibt null");
  it("getAll gibt alle Werte für key zurück");
  it("has prüft Existenz");
  it("delete entfernt alle Werte für key");
  it("set überschreibt alle Werte für key");
  it("append mit Blob-Wert");
  it("append mit filename");
  it("entries iteriert über alle Paare");
  it("keys iteriert über alle keys");
  it("values iteriert über alle values");
  it("forEach ruft Callback für jedes Paar auf");
  it("getContentType enthält multipart/form-data; boundary");
  it("toBuffer enkodiert boundary-separierten Body");
})

Cross-Referenzen

  • #30 MutationObserver-Batching: Form-Updates lösen MO-Reaktionen aus
  • #32 Event-Reihenfolge: Submit-Event + FormDataEvent müssen in korrekter Reihenfolge feuern
## Problem React 19 `<form action={fn}>` und `<button formAction={fn}>` nutzen die neue **form action** API: 1. Beim Submit erzeugt der Browser automatisch ein `FormData`-Objekt aus den Formularfeldern 2. Dispatched ein `formdata`-Event (FormDataEvent) auf dem Formular 3. Ruft die asynchrone Action-Funktion mit dem FormData-Objekt auf 4. `requestSubmit()` statt `submit()` wird verwendet, damit Submit-Events + FormData-Events dispatched werden Unsere Engine: - `FormData` gibt es nicht - `FormDataEvent` gibt es nicht - `HTMLFormElement.requestSubmit()` gibt es nicht - `form.submit()` dispatht kein submit-Event (muss es laut Spec auch nicht) ## Option A: Neuimplementierung (Empfohlen) Alle drei Komponenten als eigenständige Module implementieren, mit TDD. ### Modul 1: FormData (`src/forms/form-data.ts`) ```typescript export class FormData { private _entries: Array<[string, FormDataEntryValue]> = []; append(name: string, value: string | Blob, filename?: string): void; delete(name: string): void; get(name: string): FormDataEntryValue | null; getAll(name: string): FormDataEntryValue[]; has(name: string): boolean; set(name: string, value: string | Blob, filename?: string): void; // Iteration entries(): Iterator<[string, FormDataEntryValue]>; keys(): Iterator<string>; values(): Iterator<FormDataEntryValue>; forEach(cb: (value, key, parent) => void): void; // Encodierung getContentType(): string; // multipart/form-data; boundary=... toBuffer(): ArrayBuffer; // encode boundary-separated body } ``` **Wichtig:** FormData muss sowohl String- als auch Blob-Werte unterstützen (File-Upload). ### Modul 2: FormDataEvent (`src/forms/form-data-event.ts`) ```typescript export class FormDataEvent extends Event { readonly formData: FormData; constructor(type: string, options?: { formData: FormData }); } ``` ### Modul 3: requestSubmit + submit-event-plumbing (`src/forms/form-submission.ts`) ```typescript // Auf HTMLFormElement.prototype.requestSubmit: HTMLFormElement.prototype.requestSubmit = function(submitter?: HTMLElement) { // 1. Dispatch submit event const submitEvent = new SubmitEvent("submit", { cancelable: true, submitter }); const cancelled = !this.dispatchEvent(submitEvent); if (cancelled) return; // 2. Collect form data const fd = new FormData(this, submitter); // submitter gibt ggf. name/value // 3. Dispatch formdata event (modifiable!) const fdEvent = new FormDataEvent("formdata", { formData: fd }); this.dispatchEvent(fdEvent); // 4. If form.action is a function (React 19 pattern), call it const action = (this as any).action; if (typeof action === "function") { action(fd); } else { // Standard-HTTP-Submit (wenn action-URL angegeben) this._legacySubmit(); } }; ``` ## Option B: FormData aus dem FormData-Standard-Extraktor Es gibt ein `formdata` Polyfill auf npm — aber das deckt nur `formdata` Event ab, nicht die FormData-Klasse selbst. **Nicht empfohlen:** FormData selbst muss alle Edge Cases der multipart-Enkodierung beherrschen. ## Akzeptanzkriterien - [ ] `FormData`-Instanz erstellbar (leer und aus `<form>`-Element) - [ ] `append/delete/get/getAll/has/set` funktionieren spec-konform - [ ] String-Werte korrekt gespeichert und abrufbar - [ ] Blob-Werte korrekt gespeichert und abrufbar - [ ] `FormDataEvent` dispatcht mit formData-Property - [ ] `form.requestSubmit(submitter?)` → dispatcht submit → sammelt FormData → dispatcht formdata → ruft action-Funktion - [ ] `form.requestSubmit()` mit cancelable submit → preventDefault() bricht ab - [ ] `form.requestSubmit()` ohne action-Funktion → kein Crash - [ ] Alle bestehenden Tests bleiben grün ## Betroffene Dateien | Datei | Änderung | |-------|----------| | `src/forms/form-data.ts` | **Neu:** FormData-Klasse mit multipart-Encoding | | `src/forms/form-data-event.ts` | **Neu:** FormDataEvent extends Event | | `src/forms/form-submission.ts` | **Neu:** requestSubmit, form-action-handling | | `src/runtime-isolation.ts` | FormData + FormDataEvent in `allowedGlobals` registrieren | | `tests/unit/form-data.test.ts` | **Neu:** 15+ Tests | | `tests/unit/form-submission.test.ts` | **Neu:** 8+ Tests | | `tests/integration/react-form.test.ts` | **Neu:** Integrationstest mit React-Formular-Snippet | ## Tests (FormData) ```typescript describe("FormData", () => { it("leere FormData hat size 0"); it("append fügt String-Wert hinzu"); it("get gibt ersten Wert für key zurück"); it("get für unbekannten key gibt null"); it("getAll gibt alle Werte für key zurück"); it("has prüft Existenz"); it("delete entfernt alle Werte für key"); it("set überschreibt alle Werte für key"); it("append mit Blob-Wert"); it("append mit filename"); it("entries iteriert über alle Paare"); it("keys iteriert über alle keys"); it("values iteriert über alle values"); it("forEach ruft Callback für jedes Paar auf"); it("getContentType enthält multipart/form-data; boundary"); it("toBuffer enkodiert boundary-separierten Body"); }) ``` ## Cross-Referenzen - #30 MutationObserver-Batching: Form-Updates lösen MO-Reaktionen aus - #32 Event-Reihenfolge: Submit-Event + FormDataEvent müssen in korrekter Reihenfolge feuern
Artur closed this issue 2026-06-17 16:43:35 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
glow-all/true-headless-browser#31
No description provided.