Startseite / Artikel / Ein validierter Geburtsdatum-Formular in Next.js mit kontrollierten Eingabefeldern und Callbacks

Ein validierter Geburtsdatum-Formular in Next.js mit kontrollierten Eingabefeldern und Callbacks

Erstellen Sie ein kleines Next.js-Klientenformular, das die Eingaben mit useState verfolgt, ungültige Geburtsdaten ablehnt, zugängliche Fehler anzeigt und saubere Daten an das übergeordnete Komponenten weiterleitet.

2384 Wörter

Eine Horoskop-App benötigt von einem Besucher zwei Dinge, um eine Vorhersage erstellen zu können: einen Namen und ein Geburtsdatum. Das klingt nach einem fünfminütigen Formular, doch bereits dieser kleine Bestandteil erfordert echte Designentscheidungen: Wo wird es in einer Next.js-App ausgeführt, wem gehören die eingegebenen Werte, wie werden ungültige Datumsangaben abgelehnt und wer entscheidet, was nach einer erfolgreichen Übermittlung geschieht.

In dieser Anleitung wird dieses Formular Schritt für Schritt erstellt. Am Ende werden Sie ein eingegebenes, kontrolliertes Formularkomponente haben, das seine Eingaben validiert, Fehler verständlich meldet und nur saubere Daten weiterleitet, sowie ein klares mentales Modell, das Sie für jedes Formular mit mehr als einem Feld wiederverwenden können.

Bestimmen Sie, wofür die Komponente verantwortlich ist

Bevor Sie irgendwelchen JSX schreiben, lohnt es sich, die Aufgaben der Komponente aufzulisten. Dieses Formular hat genau drei:

  1. Das Eingetippte des Benutzers speichern.
  2. Die Eingaben vor der Übermittlung überprüfen.
  • Geben Sie gültige Daten an das Elternkomponente zurück.
  • Alles, was nicht in dieser Liste steht – wie zum Beispiel der Aufruf einer API oder die Anzeige der eingegebenen Daten – gehört an einen anderen Ort. Eine kurze Liste macht den Rest des Designs übersichtlich.

    Warum das Formular eine Client-Komponente sein muss

    In App Router ist jede Komponente standardmäßig eine Server-Komponente, es sei denn, man wählt etwas anderes aus. Server-Komponenten werden auf dem Server gerendert und enthalten kein interaktives JavaScript, weshalb sie keinen Zustand speichern oder auf Ereignisse reagieren können. Daher beginnt das Formular mit der client-Direktive.

    "use client";
    

    Die Komponente hängt von mehreren Elementen ab, die nur im Browser vorhanden sind:

    • useState für die aktuellen Werte,
    • onChange-Handler für die Eingabefelder,
    • einen onSubmit-Handler für das Formular,
    • kontinuierliche Interaktion mit dem Benutzer,
  • Eine Validierung, die vor dem Versenden ausgeführt wird.
  • Kurz gesagt zeigt das Komponente nicht nur Informationen an; es muss auf die Handlungen des Benutzers reagieren. Das ist das Signal, es als Client-Komponente zu kennzeichnen. Eine nützliche Gewohnheit ist es, solche Komponenten klein zu halten und an den Enden des Baums anzusiedeln, damit die Direktive nicht große Teile der Seite in den Client-Bundle aufnimmt.

    Geben Sie die Daten und Props ein

    Das Formular importiert einen gemeinsamen Profile-Typ zusammen mit useState.

    import { Profile } from "../types";
    import { useState } from "react";
    

    Durch die explizite Deklaration der Struktur der übermittelten Daten kann TypeScript jeden Ort prüfen, an dem sie erzeugt oder verbraucht werden, anstatt einem beliebigen Objekt den Weg durch die Anwendung zu ermöglichen.

    export type Profile ={
     name: string;
     dob: string;
    }
    

    Dann kommen die Props der Komponente. Es gibt nur eine: eine Callback-Funktion, die vom Elternteil bereitgestellt wird.

    type HoroscopeFormProps = {
      onSubmit: (info: Profile) => void;
    };
    

    Lassen Sie das Elternteil entscheiden, was als Nächstes passiert

    Dieser Eigenschaftswert bestimmt den Rand der Komponente. Das Formular sammelt Daten, hat aber keine Befugnis, selbst zu entscheiden, was damit gemacht werden soll. Je nach Anzeigeseite kann der Elternteil beispielsweise:

    • eine API aufrufen,
    • das Horoskop generieren,
    • das Profil speichern,
    • das Ergebnis anzeigen
    • oder einen anderen Zustand aktualisieren.

    Das Härtecodieren einer dieser Optionen direkt im Formular würde es an eine bestimmte Anzeigeseite binden. Stattdessen die Übernahme einer onSubmit-Funktion zu ermöglichen, sorgt dafür, dass das Formular wiederverwendet werden kann. Laut der Spezifikation erhält onSubmit ein Profile-Objekt und gibt nichts zurück (void); dadurch wird die Funktion ausgelöst und das Formular geht weiter. Der gesamte Ablauf sieht wie folgt aus:

    User enters information
            ↓
    HoroscopeForm collects it
            ↓
    HoroscopeForm validates it
            ↓
    onSubmit(user)
            ↓
    Parent decides what happens next
    

    Jeder Schritt hat einen einzigen Verantwortlichen, und der Teil des Formulars endet sofort, nachdem die Callback-Funktion aufgerufen wurde.

    Die Eingaben im React-State speichern

    Das Formular benötigt einen Ort, um die aktuellen Werte zu speichern. Drei Zustandsvariablen decken alles ab.

    const [name, setName] = useState<string>("");
    const [dob, setDob] = useState<string>("");
    const [error, setError] = useState<string>("");
    

    Schauen Sie sich zuerst den Namen an.

    const [name, setName] = useState<string>("");
    

    useState gibt ein Paar zurück. name ist der aktuelle Wert, und setName ist die Funktion, die aufgerufen wird, um ihn zu ersetzen – sie plant außerdem eine erneute Darstellung an. Der Anfangswert ist ein leeres String, weil noch nichts eingegeben wurde. Die Initialisierung mit einem String anstelle von undefined ist für kontrollierte Eingabefelder wichtig: React warnt, wenn ein Eingabefeld von unkontrolliert zu kontrolliert wechselt, sobald sein value von undefined auf einen String wechselt.

    Auch das Geburtsdatum folgt demselben Muster.

    const [dob, setDob] = useState<string>("");
    

    Die letzte Variablen enthält die aktuelle Fehlermeldung.

    const [error, setError] = useState<string>("");
    

    Eine leere Zeichenkette bedeutet, dass derzeit kein Fehler vorliegt. Die Validierung schreibt eine Nachricht in diesen Zustand, wenn etwas nicht in Ordnung ist, und löscht sie, sobald die Eingabe erfolgreich ist.

    Die Datumsvalidierung in einer eigenen Funktion halten

    Die Validierungsregeln neigen dazu, sich zu vermehren; daher wird die Überprüfung des Datums in einer speziellen Funktion durchgeführt, anstatt sie im Submit-Handler zu bündeln. Die Signatur dieser Funktion gibt Aufschluss über ihre Funktionsweise.

    function validateDOB(dob: string): string | null {
    

    Es gibt genau zwei mögliche Ergebnisse. Ein ungültiges Datum ergibt eine Zeichenkette, die das Problem erklärt; ein gültiges Datum ergibt null.

    Valid date
       ↓
    return null
    
    Invalid date
       ↓
    return error message
    

    Weil die Funktion nur eine Frage beantwortet – „Ist dieses Geburtsdatum akzeptabel?“ – ist sie leicht zu lesen, lässt sich ohne Ausgabe von Inhalten einfach unit testen und kann auf einem Server wiederverwendet werden, falls dort später ebenfalls Validierungen durchgeführt werden. Durch das Zurückgeben einer Nachricht anstelle des Werfens eines Fehlers bleibt der aufrufende Code einfach: Man prüft das Ergebnis und zeigt es bei Vorhandensein an.

    Welche Daten sollten abgelehnt werden?

    Für ein Geburtsdatum gelten zwei Regeln:

    • Keine zukünftigen Daten. Jemand kann nicht an einem noch nicht eingetretenen Tag geboren worden sein.
    • Eine sinnvolle Untergrenze. Daten, die mehr als 150 Jahre in der Vergangenheit liegen, werden abgelehnt, da sie fast sicher falsch eingegeben wurden.

    Daten erscheinen einfach, solange nicht die Tageszeit eine Rolle spielt. Ein <input type="date"> liefert einen String im Format YYYY-MM-DD, und new Date("2024-05-01") interpretiert diesen String als Mitternacht UTC, während „heute“, das mit new Date() erzeugt wird, die örtlichen Stunden, Minuten und Sekunden enthält. Je nach Zeitzone des Benutzers kann ein naiver Vergleich fälschlicherweise morgen akzeptieren oder heute ablehnen. Zwei zuverlässige Lösungen sind, beide Seiten vor dem Vergleich auf den Anfang des Tages zu normalisieren, oder die YYYY-MM-DD-Strings direkt miteinander zu vergleichen, da diese als Text korrekt sortiert werden. Unabhängig davon, welchen Ansatz Sie wählen – und egal, ob Ihnen dabei ein KI-Assistent geholfen hat – stellen Sie sicher, dass Sie erklären können, warum jeder Vergleich notwendig ist; Fehler bei Datumsverarbeitung verstecken sich genau in den Zeilen, die niemand verstanden hat.

    Koordinieren Sie alles im Submit-Handler

    Sobald der Zustand und die Validierung bereit sind, verbindet handleSubmit sie miteinander. Wenn der Benutzer abschickt, muss es Folgendes tun:

    1. die standardmäßige Absendefunktion des Browsers stoppen, die sonst die Seite neu lädt oder umleitet,
    2. bestätigen, dass beide Felder Werte enthalten,
    3. das Geburtsdatum validieren,
    4. einen Fehler anzeigen, falls etwas nicht in Ordnung ist,
    5. ansonsten die Daten an den Elternteil weiterleiten.

    Es beginnt so.

    const handleSubmit = (e: React.SubmitEvent) => {
      e.preventDefault();
    

    Standardmäßig sendet eine Formabsendung eine Anfrage und lädt die Seite neu. Da React hier die Absendung verarbeitet, muss diese Standardfunktion deaktiviert werden.

    e.preventDefault();
    

    Von diesem Punkt an entscheidet allein das Komponente, was mit dem Absenden geschieht. Eine Anmerkung zum Ereignistyp: Viele Codebasen typisieren diesen Parameter als React.FormEvent<HTMLFormElement>. Überprüfen Sie, welche Absendereignistypen Ihre installierte @types/react-Version bereitstellt, und wählen Sie den aus, der in Ihrem Projekt konsequent verwendet wird.

    Leere Felder mit einem frühen Return abweisen

    Bevor Sie überprüfen, ob das Datum sinnvoll ist, stellen Sie sicher, dass tatsächlich etwas eingegeben wurde.

    if (!name || !dob) {
      setError("Please enter in information");
      return;
    }
    

    Falls eines der Felder leer ist, protokolliert der Handler einen Fehler und kehrt umgehend zurück. Dies ist das Muster des early returns (oder Guard-Klausel): Sobald bekannt ist, dass die Eingabe ungültig ist, gibt es nichts mehr zu tun, weshalb die Funktion beendet wird, anstatt die verbleibende Logik in weitere if-Blöcke einzubetten. Jede Guard-Klausel kümmert sich um einen Fehlerfall, während der „happy path“ am Ende unverändert bleibt.

    Datumprüfung ausführen

    Sobald bekannt ist, dass ein Datum existiert, wird es durch den Validierer geprüft.

    const dobError = validateDOB(dob);
    

    Das Ergebnis ist entweder eine Nachricht oder null; daher reicht eine einzige Prüfung aus.

    if (dobError) {
      setError(dobError);
      return;
    }
    

    Eine Nachricht bedeutet, dass der Handler sie anzeigt und aufhört. null bedeutet, dass das Datum gültig ist und die Ausführung fortgesetzt wird.

    Saubere Daten an den Elternteil weitergeben

    Das Erreichen dieses Punktes bedeutet, dass alle Überprüfungen erfolgreich waren, sodass alle veralteten Fehler aus früheren Versuchen beseitigt sind.

    setError("");
    

    Dann erhält die Callback-Funktion des Elternteils das validierte Profil.

    onSubmit({ name, dob });
    

    Das ist die Folge der früheren Entwurfsentscheidung. Das Formular weiß nicht und kümmert sich auch nicht darum, was als Nächstes passiert; es gibt lediglich an, dass gültige Daten verfügbar sind, und das Elternteil entscheidet, was zu tun ist. Derselbe Komponente könnte heute einen Horoskop-Generator und morgen ein Profil-Einstellungsformular ohne Änderungen versorgen.

    Verbinden der Logik mit dem Markup

    Das Formularelement verknüpft die Abgabe mit dem Handler.

    <form onSubmit={handleSubmit}>
    

    Dies weist React an, handleSubmit auszuführen, sobald das Formular abgesendet wird – egal ob durch Klicken auf die Schaltfläche oder Drücken von Enter in einem Feld. Als Nächstes kommt das Name-Feld.

    <input
      type="text"
      value={name}
      onChange={(e) => setName(e.target.value)}
    />
    

    Wie ein kontrolliertes Eingabefeld synchron bleibt

    Das ist eine kontrollierte Eingabe: Der React-State, nicht das DOM, ist die Quelle der Wahrheit für seinen Wert. Immer dann, wenn der Benutzer eintippt, wird der Änderungshandler ausgeführt.

    onChange={(e) => setName(e.target.value)}
    

    Er liest den neuen Text aus dem Ereignis und speichert ihn im State. Der vollständige Loop sieht so aus:

    User types
        ↓
    onChange fires
        ↓
    setName(new value)
        ↓
    name state updates
        ↓
    value={name}
        ↓
    Input displays updated value
    

    Weil die Eingabe stets das anzeigt, was name enthält, ist garantiert, dass der von Ihnen überprüfte Wert derselbe ist wie der auf dem Bildschirm. Das Datefeld verwendet dasselbe Muster.

    <input
      type="date"
      value={dob}
      onChange={(e) => setDob(e.target.value)}
    />
    

    Der State verfolgt das ausgewählte Datum, und jede Änderung ruft setDob auf. Eine Ergänzung, die in Betracht gezogen werden sollte, ist das native max-Attribut, das auf das heutige Datum gesetzt wird. Dadurch werden die meisten Datumsauswahler davon abgehalten, zukünftige Tage anzuzeigen, während Ihr Validator weiterhin vor eingegebenen Daten und älteren Browsern schützt.

    Jeder Eingabefeld sollte außerdem eine sichtbare <label>-Elemente haben. Ein Platzhalter oder ein nahegelegenes Überschriftselement ist kein Ersatz; das Label ist es, das von Screen Readers ausgesprochen wird und das das Feld durch seine Beschriftung anklickbar macht.

    Zeigen Sie Fehler nur, wenn sie vorhanden sind

    Die Fehlernachricht sollte nur dann angezeigt werden, wenn ein Fehler vorliegt. Bedingte Darstellung kümmert sich darum.

    {error && (
      <p role="alert">
        {error}
      </p>
    )}
    

    Wenn error einen Text enthält, wird der Absatz angezeigt; wenn es sich um eine leere Zeichenkette handelt, die als falsch gilt, wird nichts angezeigt. Der Kurzweg && ist hier sicher, da der Wert eine Zeichenkette ist. Bei Zahlen kann das jedoch zu Fehlern führen: Eine Zahl von 0 würde als das Literall „0“ angezeigt werden.

    Der Absatz besitzt außerdem eine ARIA-Rolle.

    role="alert"
    

    role="alert" teilt der assistiven Technologie mit, dass dieser Inhalt wichtig und zeitkritisch ist, sodass Bildschirmleser ihn sofort mitteilen, sobald er erscheint. Es handelt sich um eine Änderung nur eines Attributs, die es ermöglicht, Validierungsfeedback für Menschen nutzbar zu machen, die die Anzeige nicht sehen können. Zur besseren Klarheit kann das fehlerhafte Feld auch mit aria-invalid markiert und über aria-describedby mit der entsprechenden Nachricht verknüpft werden.

    Fügen Sie die Absendetaste hinzu

    Der letzte Bestandteil ist eine Taste, die ausdrücklich als Absendetaste deklariert wird.

    <button type="submit">
      Submit
    </button>
    

    In einem Formular löst ein Button mit type="submit" die Methode onSubmit des Formulars aus, wodurch auch handleSubmit aufgerufen wird. Buttons in einem Formular haben standardmäßig ohnehin die Eigenschaft „Submit“, doch die explizite Angabe des Types verhindert Überraschungen, falls später ein zweiter Button hinzugefügt wird, der zu einem anderen Zweck dient, beispielsweise um die Felder zu leeren.

    Der vollständige Datenfluss

    Insgesamt bewegt sich die Komponente die Daten in eine Richtung:

    State
      ↓
    User input
      ↓
    Submit
      ↓
    Validation
      ↓
    Parent callback
    
    • useState speichert den von dem Benutzer eingegebenen Inhalt.
    • Die Eingabefelder aktualisieren diesen Zustand bei jeder Änderung.
    • Durch das Absenden des Formulars wird handleSubmit ausgeführt.
    • handleSubmit prüft die eingegebenen Werte.
    • Falsche Eingaben setzen den Fehlerzustand und stoppen den Vorgang.
    • Gültige Eingaben werden über onSubmit an die Elternkomponente weitergeleitet, wo sie dort verarbeitet werden.

    Was kommt als Nächstes?

    Der manuell erstellte Ansatz eignet sich ideal zum Lernen und ist für Formulare mit zwei Feldern völlig ausreichend. Wenn die Formulare viele Felder sowie regelbasierte Überprüfungen zwischen Feldern oder Server-Prüfungen benötigen, sollten Sie eine Schema-Bibliothek in Betracht ziehen, damit dieselben Regeln sowohl auf der Client- als auch auf der Serverseite angewendet werden können; eine gemeinsame Zod-Schema-Nutzung im React-Frontend und Node-Backend ist eine Möglichkeit, dies umzusetzen. Client-seitige Validierung verbessert das Benutzererlebnis, ersetzt aber niemals die Server-Validierung, da jede Anfrage manuell erstellt werden kann.

    Haupterkenntnisse

    • Markieren Sie nur interaktive Komponenten mit "use client" und halten Sie sie klein.
    • Geben Sie dem Formular eine einzige Aufgabe: Daten sammeln, validieren und weiterleiten. Lassen Sie den Elternteil die Nebeneffekte über eine typisierte Callback-Funktion steuern.
  • Verwenden Sie kontrollierte Eingabefelder, die mit Zeichenketten initialisiert werden, damit der Wert auf dem Bildschirm derselbe ist, den Sie überprüfen.
  • Platzieren Sie die Validierungsregeln in reinen Funktionen, die eine Nachricht oder null zurückgeben; sie sind leicht zu testen und wiederverwendbar.
  • Gehen Sie bei Datumsangaben sorgfältig vor: Normalisieren Sie die Tageszeit oder vergleichen Sie YYYY-MM-DD-Zeichenketten, um Fehler durch Zeitzone-Unterschiede zu vermeiden.
  • Verwenden Sie frühzeitige Rückgänge, um den Absendehandler übersichtlich zu halten, sowie role="alert" zusammen mit geeigneten Beschriftungen, damit Fehler leicht zugänglich sind.
  • Dass man erklären kann, warum jede Zeile vorhanden ist – insbesondere solche, die von einem KI-Assistenten vorgeschlagen wurden – gehört dazu, die Arbeit abzuschließen.