Event Handler Types in React richtig einsetzen
AI generated
<T>
type
TypeScript · React · Events · Formulare
Event Handler Types in React richtig einsetzen
SyntheticEvent, ChangeEvent und MouseEventHandler ohne any

Ein Event-Handler ohne korrekten Typ verleitet fast automatisch zu any oder zu wiederholten Typumwandlungen, sobald auf event.target.value zugegriffen wird. React Event Handler Types lösen dieses Problem an der Wurzel: SyntheticEvent, ChangeEvent und die passenden Handler-Typen wie MouseEventHandler kennen exakt, welches Element ein Event ausgelöst hat und welche Eigenschaften darauf verfügbar sind. Dieser Artikel zeigt, wie man Event Handler Types in React systematisch und ohne any einsetzt.

13 Min. Lesezeit SyntheticEvent · ChangeEvent · MouseEventHandler TypeScript 5.x · React 18/19

1. Warum React-Events eigene Typen brauchen

React reicht Browser-Events nicht direkt an Event-Handler weiter, sondern verpackt sie in ein eigenes Wrapper-Objekt, das SyntheticEvent. Dieses Wrapper-Objekt normalisiert Unterschiede zwischen Browsern und sorgt für konsistentes Verhalten über alle Zielplattformen hinweg. Für die Typisierung bedeutet das, dass native DOM-Typen wie Event oder MouseEvent aus dem lib.dom.d.ts nicht automatisch zu React-Events passen, sondern eigene, React-spezifische Event Handler Types nötig sind, die React selbst über das @types/react-Paket bereitstellt.

Ohne diese spezifischen Typen greifen viele Entwickler entweder zu any für den Event-Parameter oder zu einem zu allgemeinen Event, was beides dazu führt, dass Properties wie event.target.value nicht mehr typgeprüft sind. Ein Tippfehler in der Property-Kette fällt dann erst zur Laufzeit auf, oft als undefined, das stillschweigend weiterverarbeitet wird, statt als Compile-Fehler direkt im Editor sichtbar zu werden. Die korrekten React Event Handler Types verhindern genau das, weil sie exakt beschreiben, welches Element den Event ausgelöst hat und welche Eigenschaften auf diesem Element existieren.

Ein weiterer Grund für präzise Event-Typen ist die Autovervollständigung im Editor. Sobald ein Event-Handler korrekt typisiert ist, schlägt der Editor bei event. exakt die verfügbaren Properties vor, inklusive spezifischer Felder wie key bei Keyboard-Events oder clientX bei Maus-Events. Diese Unterstützung verschwindet komplett, sobald der Parameter als any deklariert ist.

2. SyntheticEvent verstehen: React-Event vs. natives DOM-Event

SyntheticEvent ist die Basisklasse aller React-Event-Typen und bildet die gemeinsame Schnittmenge an Properties ab, die praktisch jedes Event besitzt: preventDefault(), stopPropagation(), target und currentTarget. Für konkrete Event-Arten existieren spezialisierte Subtypen wie MouseEvent, KeyboardEvent, ChangeEvent und FormEvent, jeweils aus dem react-Paket importiert, nicht aus den globalen DOM-Typdefinitionen. Der Namenskonflikt mit den gleichnamigen nativen DOM-Typen ist dabei beabsichtigt: React signalisiert damit, dass es sich um die React-spezifische Variante handelt, weshalb der Import explizit aus react erfolgen muss.

Ein häufiges Missverständnis betrifft die Wiederverwendung von Event-Objekten außerhalb des synchronen Handler-Aufrufs, etwa in einem setTimeout oder nach einem await. In älteren React-Versionen wurden SyntheticEvent-Objekte nach dem Handler-Aufruf recycelt, in aktuellen Versionen ist dieses Pooling entfernt, aber der Zugriff auf event.target nach einem asynchronen Vorgang bleibt trotzdem riskant, weil das zugrunde liegende DOM-Element sich zwischenzeitlich verändert haben kann. Wer einen Wert aus dem Event über eine asynchrone Grenze hinweg braucht, sollte ihn deshalb synchron in eine lokale Variable extrahieren.


import type { ChangeEvent, MouseEvent, FormEvent } from "react";

// SyntheticEvent types are imported from "react", not from lib.dom.d.ts
function handleClick(event: MouseEvent<HTMLButtonElement>) {
  event.preventDefault();
  console.log(event.currentTarget.name); // typed: HTMLButtonElement
}

function handleChange(event: ChangeEvent<HTMLInputElement>) {
  console.log(event.target.value); // typed: string
}

function handleSubmit(event: FormEvent<HTMLFormElement>) {
  event.preventDefault();
  // Extract needed values synchronously before any async boundary
  const formData = new FormData(event.currentTarget);
  console.log(formData.get("email"));
}

3. Die häufigsten Handler-Typen: onClick, onChange, onSubmit

Für die drei häufigsten Interaktionen in React-Anwendungen gibt es feste, gut dokumentierte Event Handler Types: MouseEvent<HTMLButtonElement> für Klick-Handler auf Buttons, ChangeEvent<HTMLInputElement> für Änderungen an Eingabefeldern, und FormEvent<HTMLFormElement> für das Absenden eines Formulars. Der generische Typparameter in spitzen Klammern gibt dabei jeweils an, an welchem HTML-Element der Handler tatsächlich registriert ist, und bestimmt darüber den konkreten Typ von event.currentTarget.

Wichtig ist, den Typparameter korrekt auf das tatsächliche Element abzustimmen: Ein onClick-Handler an einem <div> braucht MouseEvent<HTMLDivElement>, nicht HTMLButtonElement, sonst schlägt der Compiler beim Zugriff auf button-spezifische Properties wie disabled fehl, oder schlimmer, lässt einen falschen Zugriff unbemerkt zu, wenn beide Elementtypen zufällig dieselbe Property besitzen. Diese Präzision zahlt sich vor allem bei komplexeren Interaktionen aus, in denen ein Handler an mehreren unterschiedlichen Elementtypen registriert werden könnte.


import { useState } from "react";
import type { ChangeEvent, FormEvent } from "react";

function LoginForm() {
  const [email, setEmail] = useState("");

  // Typed to the exact element the handler is attached to
  const handleEmailChange = (event: ChangeEvent<HTMLInputElement>) => {
    setEmail(event.target.value);
  };

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    console.log("Submitting", email);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" value={email} onChange={handleEmailChange} />
      <button type="submit">Anmelden</button>
    </form>
  );
}

4. Generische Event-Handler für Formularfelder

Formulare mit vielen einzelnen Feldern führen häufig zu wiederholten, fast identischen onChange-Handlern, jeweils nur mit einem anderen Feldnamen im setState-Aufruf. Statt für jedes Feld einen eigenen Handler zu schreiben, lässt sich ein einziger, generischer Handler implementieren, der den Feldnamen aus dem name-Attribut des Eingabeelements abliest. Der Event Handler Type bleibt dabei unverändert ChangeEvent<HTMLInputElement>, während der generische Zustandstyp T beschreibt, welche Felder das jeweilige Formular tatsächlich besitzt.

Diese Technik reduziert Duplizierung erheblich, verlangt aber eine bewusste Entscheidung an einer Stelle: Der Zugriff auf event.target.name als Schlüssel für ein generisches Zustandsobjekt ist von Natur aus etwas weniger streng typisiert als ein Handler pro Feld, weil TypeScript zur Kompilierzeit nicht garantieren kann, dass jeder name-Wert im JSX auch tatsächlich einem Feld im Zustandstyp entspricht. Diesen Kompromiss sollte man bewusst eingehen, meist über eine einzelne, gut sichtbare Typ-Assertion an der Stelle, an der der Feldname als Schlüssel verwendet wird, statt die Unsicherheit unbemerkt durch die gesamte Komponente zu tragen.


import { useState } from "react";
import type { ChangeEvent } from "react";

interface RegistrationForm {
  email: string;
  username: string;
  city: string;
}

function RegistrationFields() {
  const [form, setForm] = useState<RegistrationForm>({ email: "", username: "", city: "" });

  // One generic handler instead of one handler per field
  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    const { name, value } = event.target;
    setForm((prev) => ({ ...prev, [name as keyof RegistrationForm]: value }));
  };

  return (
    <>
      <input name="email" value={form.email} onChange={handleChange} />
      <input name="username" value={form.username} onChange={handleChange} />
      <input name="city" value={form.city} onChange={handleChange} />
    </>
  );
}

5. currentTarget vs. target typsicher unterscheiden

Ein Detail, das bei der Typisierung von Event-Handlern regelmäßig übersehen wird, ist der Unterschied zwischen event.target und event.currentTarget. currentTarget ist immer genau das Element, an dem der Handler registriert wurde, und trägt exakt den generischen Typparameter, der beim Event-Typ angegeben wurde, etwa HTMLButtonElement. target dagegen ist das Element, das das Event ursprünglich ausgelöst hat, was bei Event-Bubbling ein beliebiges Kindelement des registrierten Elements sein kann, und ist deshalb typischerweise als breiterer EventTarget typisiert, der weniger spezifische Properties bietet.

Für die meisten Event Handler Types in React ist currentTarget die richtige Wahl, weil es zuverlässig auf das Element mit der bekannten, erwarteten Struktur zeigt. Ein Klick-Handler an einem <button>, der über currentTarget.dataset auf ein Data-Attribut zugreift, bekommt vom Compiler exakt die Properties eines HTMLButtonElement vorgeschlagen. Greift man stattdessen auf target zu, weil das Event tatsächlich von einem verschachtelten <span> innerhalb des Buttons ausgelöst wurde, ist eine explizite Typumwandlung oder ein Typ-Guard nötig, um button-spezifische Properties sicher zu verwenden.


import type { MouseEvent } from "react";

function IconButton() {
  const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
    // currentTarget: always the button the handler is attached to
    console.log(event.currentTarget.dataset.action);

    // target: whichever element within the button actually triggered the event
    // (could be an inner <svg> or <span>), typed more loosely as EventTarget
    if (event.target instanceof HTMLElement) {
      console.log(event.target.tagName);
    }
  };

  return (
    <button data-action="delete" onClick={handleClick}>
      <span>Löschen</span>
    </button>
  );
}

6. Event-Handler als Props: MouseEventHandler und Co.

Wird ein Event-Handler nicht innerhalb der Komponente selbst definiert, sondern als Prop von außen übergeben, ist die einzeilige Handler-Typ-Familie mit dem Suffix Handler die kompaktere Alternative zur ausgeschriebenen Funktionssignatur. MouseEventHandler<HTMLButtonElement> ist funktional identisch zu (event: MouseEvent<HTMLButtonElement>) => void, liest sich aber deutlich kürzer in einem Props-Interface und macht auf den ersten Blick klar, dass es sich um einen Event-Handler und nicht um einen beliebigen Callback handelt.

Diese kompakte Schreibweise existiert für praktisch jeden gängigen Event-Typ: ChangeEventHandler, FormEventHandler, KeyboardEventHandler, FocusEventHandler. Alle folgen demselben Muster und erwarten denselben generischen Elementtyp wie ihr Event-Pendant. Für Props-Interfaces mit mehreren Event-Handlern verbessert diese Namenskonvention die Lesbarkeit erheblich, weil das Suffix Handler im Interface sofort erkennbar macht, um welche Art von Prop es sich handelt, ohne die vollständige Funktionssignatur lesen zu müssen.


import type { MouseEventHandler, ChangeEventHandler } from "react";

interface EditableRowProps {
  onDelete: MouseEventHandler<HTMLButtonElement>;
  onLabelChange: ChangeEventHandler<HTMLInputElement>;
  label: string;
}

// Equivalent to: onDelete: (event: MouseEvent<HTMLButtonElement>) => void
function EditableRow({ onDelete, onLabelChange, label }: EditableRowProps) {
  return (
    <div>
      <input value={label} onChange={onLabelChange} />
      <button onClick={onDelete}>Löschen</button>
    </div>
  );
}

7. Keyboard- und Focus-Events typsicher behandeln

KeyboardEvent bringt neben den Basis-Properties von SyntheticEvent zusätzlich spezifische Felder wie key, code und die Modifier-Flags shiftKey, ctrlKey und altKey mit. Für Tastatur-Interaktionen, etwa das Bestätigen eines Formulars mit der Enter-Taste oder das Schließen eines Dialogs mit Escape, prüft man event.key gegen einen String, wobei der Compiler auch hier die exakten Property-Namen vorschlägt, statt sich auf veraltete, seit langem missbilligte Properties wie keyCode zu verlassen.

FocusEvent folgt demselben Muster für onFocus- und onBlur-Handler und ergänzt zusätzlich das Feld relatedTarget, das bei onBlur das Element beschreibt, zu dem der Fokus als Nächstes wechselt. Ein typischer Anwendungsfall ist ein Dropdown, das sich schließen soll, sobald der Fokus das Dropdown und seine Kindelemente vollständig verlässt, aber offen bleiben soll, solange relatedTarget noch innerhalb des Dropdown-Containers liegt.


import { useRef } from "react";
import type { KeyboardEvent, FocusEvent, ReactNode } from "react";

function SearchBox() {
  // Keyboard event: "key" is a proper string, no deprecated keyCode needed
  const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
    if (event.key === "Enter") {
      console.log("Search confirmed");
    }
    if (event.key === "Escape") {
      event.currentTarget.blur();
    }
  };

  return <input type="text" onKeyDown={handleKeyDown} />;
}

function Dropdown({ children }: { children: ReactNode }) {
  const containerRef = useRef<HTMLDivElement>(null);

  // relatedTarget: the element focus is moving to, may be null
  const handleBlur = (event: FocusEvent<HTMLDivElement>) => {
    if (!containerRef.current?.contains(event.relatedTarget)) {
      console.log("Focus left the dropdown entirely, closing it");
    }
  };

  return (
    <div ref={containerRef} onBlur={handleBlur} tabIndex={-1}>
      {children}
    </div>
  );
}

8. Eigene Callback-Signaturen statt nativer Events

Nicht jeder Callback, den eine Komponente nach außen anbietet, sollte zwangsläufig ein natives SyntheticEvent weiterreichen. Eine Select-Komponente, die eine Option auswählt, oder eine Rating-Komponente, die einen Sternewert liefert, kommuniziert nach außen sinnvoller über eine eigene, domänenspezifische Callback-Signatur, etwa onSelect: (option: Product) => void, statt den internen ChangeEvent oder MouseEvent unverändert durchzureichen. Die aufrufende Komponente interessiert sich für das ausgewählte Produkt, nicht für Implementierungsdetails wie das zugrunde liegende native Element.

Diese Entkopplung hat einen praktischen Vorteil für Event Handler Types: Ändert sich intern, mit welchem HTML-Element eine Komponente eine Interaktion umsetzt, etwa der Wechsel von einem nativen <select> zu einer Liste aus <div>-Elementen für mehr Styling-Freiheit, bleibt die nach außen sichtbare Callback-Signatur davon vollständig unberührt. Die Komponente extrahiert intern die relevanten Daten aus dem jeweiligen nativen Event und reicht ausschließlich diese Daten über die eigene, stabile Signatur weiter.


interface Product { id: number; name: string; price: number; }

interface ProductPickerProps {
  products: Product[];
  // Domain-specific callback instead of a raw native event
  onSelect: (product: Product) => void;
}

function ProductPicker({ products, onSelect }: ProductPickerProps) {
  // Internal native event is translated into the domain callback here
  const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
    const selected = products.find((p) => String(p.id) === event.target.value);
    if (selected) onSelect(selected);
  };

  return (
    <select onChange={handleChange}>
      {products.map((p) => (
        <option key={p.id} value={p.id}>{p.name}</option>
      ))}
    </select>
  );
}

// Caller only ever sees a typed Product, never the underlying ChangeEvent
// <ProductPicker products={products} onSelect={(product) => console.log(product.price)} />

9. Event-Handler-Typisierung im Vergleich

Die folgende Übersicht fasst zusammen, welcher Ansatz zur Typisierung von Event Handlern für welches Szenario geeignet ist.

Szenario Unpassender Ansatz Empfohlener Ansatz Vorteil
Handler direkt in der Komponente (event: any) => void (event: ChangeEvent<HTMLInputElement>) => void Autovervollständigung und geprüfte Properties
Handler als Prop von außen ausgeschriebene Funktionssignatur MouseEventHandler<HTMLButtonElement> Kompakter, sofort als Handler erkennbar
Zugriff auf das registrierte Element event.target event.currentTarget Zuverlässig der Typ des registrierten Elements
Öffentliche Komponenten-API natives Event unverändert durchreichen eigene Callback-Signatur wie onSelect(item: T) Interne Implementierung bleibt austauschbar
Viele fast identische Feld-Handler ein Handler pro Formularfeld ein generischer Handler über name Deutlich weniger duplizierter Code

Kein einzelner Ansatz ist universell richtig. Für einfache, lokale Handler reicht die konkrete Event-Typisierung völlig aus. Sobald ein Handler jedoch als Prop nach außen sichtbar wird, lohnt sich entweder die kompakte Handler-Typ-Familie oder, bei domänenspezifischen Komponenten, eine eigene Callback-Signatur, die native Event-Details bewusst verbirgt.

Mironsoft

TypeScript-Tooling, typsichere React-Formulare und Magento/Hyvä-Integrationen

Event-Handler ohne any, dafür mit voller Typprüfung?

Wir prüfen bestehende React-Komponenten auf lose typisierte Events, ersetzen any-Handler durch korrekte SyntheticEvent-Typen und bauen typsichere Formular- und Interaktionslogik für euer Frontend.

Event-Audit

Bestehende Handler auf any und lose Event-Typen prüfen

Formular-Refactoring

Generische, typsichere Handler für komplexe Formulare einführen

Komponenten-API

Domänenspezifische Callback-Signaturen statt roher Events

10. Zusammenfassung

Event Handler Types in React beginnen mit dem Verständnis, dass React eigene, aus dem react-Paket importierte Event-Typen bereitstellt, die sich von nativen DOM-Typen unterscheiden. MouseEvent, ChangeEvent und FormEvent decken zusammen mit ihrem generischen Elementtyp-Parameter die häufigsten Interaktionen ab, während die kompakte Handler-Typ-Familie sich für Event-Handler eignet, die als Props nach außen gereicht werden. currentTarget bleibt für die meisten Fälle die verlässlichere Wahl gegenüber target, weil es exakt den Typ des registrierten Elements trägt.

Für Formulare mit vielen Feldern reduziert ein generischer Handler über das name-Attribut Duplizierung erheblich, verlangt aber eine bewusste, sichtbare Typ-Assertion an einer einzigen Stelle. Öffentliche Komponenten-APIs profitieren davon, native Events durch eigene, domänenspezifische Callback-Signaturen zu ersetzen, damit interne Implementierungsdetails austauschbar bleiben, ohne die nach außen sichtbare Schnittstelle zu verändern. Wer diese Prinzipien konsequent anwendet, ersetzt any-Handler durch Code, der Tippfehler und falsche Property-Zugriffe bereits beim Kompilieren meldet.

Event Handler Types in React - Das Wichtigste auf einen Blick

SyntheticEvent

React-eigene Event-Typen aus dem react-Paket, nicht aus lib.dom.d.ts, mit generischem Elementtyp-Parameter.

currentTarget vs. target

currentTarget trägt zuverlässig den Typ des registrierten Elements, target ist bei Bubbling breiter typisiert.

Handler als Props

MouseEventHandler, ChangeEventHandler und Co. als kompakte Alternative zur ausgeschriebenen Funktionssignatur.

Öffentliche APIs

Domänenspezifische Callbacks statt roher Events, damit interne Implementierungsdetails austauschbar bleiben.

11. FAQ: Event Handler Types in React

1Warum keine nativen DOM-Event-Typen verwenden?
React verpackt Events in SyntheticEvent zur Normalisierung. Die passenden Typen müssen aus dem react-Paket importiert werden.
2Was bedeutet der Parameter in ChangeEvent<HTMLInputElement>?
Er legt fest, an welchem Element der Handler registriert ist, und bestimmt den Typ von event.currentTarget.
3target vs. currentTarget?
currentTarget ist immer das registrierte Element mit dem angegebenen Typ. target ist das ursprünglich auslösende Element, breiter typisiert.
4Wann MouseEventHandler statt Funktionssignatur?
Wenn der Handler als Prop übergeben wird. Kürzer und sofort als Event-Handler erkennbar.
5Generischer Handler für viele Formularfelder?
Ein ChangeEvent-Handler, der name als Schlüssel im Zustandsobjekt nutzt, mit bewusster Typ-Assertion an einer Stelle.
6Native Events nicht einfach durchreichen, warum?
Interne Implementierungsänderungen würden sonst die öffentliche Schnittstelle beeinflussen. Eine eigene Callback-Signatur bleibt stabil.
7event.target nach await sicher?
Riskant, das DOM-Element kann sich verändert haben. Benötigte Werte synchron vorher extrahieren.
8Zusätzliche Properties bei KeyboardEvent?
key, code und die Modifier-Flags shiftKey, ctrlKey, altKey, statt veralteter Properties wie keyCode.
9Was liefert relatedTarget bei FocusEvent?
Bei onBlur das Element, zu dem der Fokus wechselt. Nützlich, um zu prüfen, ob ein Container wirklich verlassen wird.
10Warum ist any als Event-Typ problematisch?
Schaltet jede Typprüfung ab. Tippfehler in der Property-Kette fallen erst zur Laufzeit auf, nicht schon beim Kompilieren.