Startseite / Artikel

Entwurf von React-Bildschirmen für Lade-, leere-, Fehler- und Wiederholungszeiten

Lernen Sie, alle möglichen Zustände einer React-Oberfläche und eines Formulars abzubilden – von Skeletten und Neuladungen über leere Ergebnisse, nützliche Fehlermeldungen bis hin zu Schutz vor doppeltem Absenden.

Verlag: REACTAPP.TOP6 Min. Lesezeit1319 Wörter

Quellen: Your React App Works. Until the API Doesn’t.

Ein Bildschirm, der in einer lokalen Datenbank perfekt dargestellt wird, kann in der Produktion dennoch versagen, wenn die Benutzer auf langsame Netzwerke, leere Tabellen und fehlerhafte Endpunkte stoßen. Diese Anleitung zeigt die verschiedenen Zustände auf, durch die ein datengetriebener React-Bildschirm sowie seine Formulare tatsächlich gehen, und liefert eine Checkliste zur Gestaltung jedes Zustands, bevor Sie Code in JSX schreiben.

Das Denkmuster hinter den meisten anfälligen Bildschirmen

Wenn ein Komponentenentwurf erstellt wird, sieht der Ablauf in der Regel wie eine gerade Linie aus: Etwas anfordern und anschließend darstellen.

Fetch data → Show data

Bei diesem Ansatz gibt es keinen Platz für Fehler. Ein Produktionsbildschirm weist verschiedene Pfade auf: Er beginnt mit dem Laden, landet entweder bei einem Erfolg oder einem leeren Ergebnis, kann in einen Fehler fallen und benötigt eine Möglichkeit, über Wiederholungsversuche oder Neuladungen wieder zurückzukehren.

Loading → Success → Empty
             ↓
           Error
             ↓
        Retry / Refetch

Laden als eigener Zustand mit eigenem Design betrachten

„Zeigen Sie einen Spinner an“ klingt genauso wie „Umhüllen Sie es mit try/catch“ nach einer vollständigen Lösung: Es ist nicht falsch, deckt aber selten das aus, was die Benutzer tatsächlich erleben. Zwei Fragen zeigen den Unterschied auf: Wie sieht der Bildschirm aus, wenn die Antwort vier Sekunden dauert? Und was passiert, wenn sie in 40 Millisekunden ankommt – sieht der Benutzer dann einen Spinner, der nur einen Frame lang blinkt, und empfindet die Benutzeroberfläche als ruckelig?

Anfangsladung versus Neuabruf

Es hilft, das Laden in zwei unterschiedliche Situationen aufzuteilen:

  • Anfangsladung. Noch ist nichts auf dem Bildschirm zu sehen, daher ist ein Skelett, das die endgültige Anordnung widerspiegelt, eine vernünftige und sogar angenehme Wahl.
  • Neuabruf. Die Daten sind bereits sichtbar, zum Beispiel nachdem der Benutzer „Erneuern“ geklickt hat oder einen Filter geändert hat. Das Löschen dieser Daten und Ersetzen durch einen Vollseiten-Loader führt zum Verlust nützlichen Kontexts.

Während eines erneuten Abrufs sollten die vorherigen Daten weiter angezeigt werden, und in der Nähe des aktualisierten Elements sollte ein kleiner inline-Indikator sichtbar sein. Spinner geben niemandem Sicherheit; im besten Fall werden sie hingenommen. Bei sehr schnellen Antworten sollte man in Betracht ziehen, den Indikator kurz zu verzögern, sodass er erst dann erscheint, wenn das Warten bemerkbar wird.

Warum ein funktionierender Fetch-Aufruf nur ein kleiner Teil der Aufgabe ist

Der Anfrage selbst liegt das Einfachste zugrunde:

const res = await fetch("/api/students");
const data = await res.json();
setStudents(data);

Was dieser Codeausschnitt unbeantwortet lässt, sind alle Aspekte drumherum. Was sieht der Benutzer, während die Promise aussteht? Was passiert, wenn fetch abgelehnt wird oder der Server mit einem nicht-2xx-Status antwortet (was fetch an sich nicht als Fehler betrachtet)? Was, wenn der Datenkörper ein leeres Array ist? Die wichtige Frage lautet nicht, wie man die Daten erhält, sondern in welchen Zuständen sich diese Anfrage zu jedem Zeitpunkt befinden kann.

Falls students der einzige Zustandswert ist, muss der Rest der Benutzeroberfläche aus der Tatsache ableiten, ob diese Variable null ist, leer oder mit Daten gefüllt. null kann bedeuten „wird noch geladen“ oder „die Anfrage ist fehlgeschlagen“, und es gibt keine Möglichkeit, diese Fälle voneinander zu unterscheiden. Das ist Raten, das als Zustandsverwaltung getarnt wird.

Ein leeres Ergebnis ist ein Erfolg, kein Fehler

Dieser Punkt verdient besondere Betonung, denn viele Codebasen verstehen ihn falsch. Eine 200 OK-Antwort mit [] bedeutet, dass der Server seine Aufgabe korrekt erledigt hat: Er meldet lediglich, dass es nichts zu zeigen gibt.

"Keine Schüler gefunden" und ">Fehler beim Laden der Schüler" beschreiben völlig unterschiedliche Situationen, doch viele Apps verwenden für beide denselben grauen Platzhalter. Ein gemeinsamer <p>Etwas ist schiefgelaufen</p> für beide Fälle verschmilzt zwei Bedingungen, die unterschiedliche Lösungen erfordern: Ein leeres Zustand könnte den Benutzer dazu auffordern, einen Filter zu löschen, während ein Fehlerzustand die Ursache erklären und eine Wiederherstellung anbieten sollte.

Wie ein nützlicher Fehlerzustand aussieht

Der allgemeine Satz "Etwas ist schiefgelaufen" hilft niemandem. Ein besserer Fehlerzustand:

  • teilt dem Benutzer mit, was er tun kann, wie zum Beispiel seine Verbindung zu überprüfen oder später erneut zu versuchen;
  • bietet einen Wiederholen-Button an, wenn eine erfolgreiche Wiederholung realistisch ist;
  • gibt niemals einen rohen Stack-Trace oder interne Fehlerdaten in der Benutzeroberfläche an;
  • bleibt auf dem Teil des Bildschirms begrenzt, bei dem der Fehler aufgetreten ist.

Der letzte Punkt ist der, der am häufigsten übersprungen wird. Auf einem Dashboard mit fünf Widgets sollte eine fehlerhafte Anfrage nicht dazu führen, dass die vier erfolgreich geladenen Elemente ausgeblendet werden. Fehlerbehandlung nach Abschnitten – entweder über lokalen Zustand oder durch Fehlergrenzen um unabhängige Bereiche.

Formulare durchlaufen ihren eigenen Lebenszyklus

Das Schreiben von Daten weist sogar noch mehr Zustände auf als das Lesen davon:

Idle → Editing → Submitting → Success / Error

Schutz vor doppelten Einreichungen

Betrachten wir einen Benutzer, der dreimal auf „Senden“ klickt, weil die Schaltfläche nach den ersten beiden Klicks keine sichtbare Rückmeldung gab. Sofern die Schaltfläche während des Sendevorgangs nicht deaktiviert ist oder der Handler ein isSubmitting-Flag überprüft und frühzeitig zurückgegeben wird, wenn bereits eine Anfrage unterwegs ist, erhält der Server drei Anfragen. Bei einem Bestellformular bedeutet das, dass ein Kunde dreimal für ein einziges T-Shirt berechnet wird. Bei Zahlungen sollte die Backend-Plattform ebenfalls Duplikate entfernen, beispielsweise mithilfe einer Idempotenzschlüssel.

Bewahrung der Benutzereingaben bei fehlgeschlagenem Sendevorgang

Falls die Anfrage fehlschlägt, sollte das Formular weiterhin ausgefüllt bleiben. Jemanden dazu zu zwingen, eine lange Nachricht nach einem Netzwerkproblem erneut einzugeben, verschwendet seine Zeit. Validierungsfehler – egal ob sie von Client-Seiten-Regeln oder vom Server stammen – sollten neben dem jeweiligen Feld angezeigt werden und nicht in einem unbestimmten Banner oben im Formular.

Verteilter Boolesche Werte durch benannte Zustände ersetzen

Wenn sie unüberprüft bleiben, neigt ein Komponenten dazu, eine Reihe solcher Flags anzuhäufen:

isLoading, isError, isSubmitting, hasData, isEmpty

Jedes Flag ist für sich genommen sinnvoll. Zusammen ermöglichen fünf Boolesche Werte 32 Kombinationen, von denen nur wenige sinnvoll sind, und nichts hindert daran, dass isLoading && isError gleichzeitig wahr sind.

Eine Liste von Studenten erfordert keine formelle State-Machine-Bibliothek. Was sie stattdessen braucht, ist eine bewusste Entscheidung – bereits vor dem ersten Aufruf von useState – darüber, welche Zustände der Bildschirm annehmen kann. Repräsentieren Sie den aktuellen Zustand mit einem einzigen Wert, beispielsweise einem status-Feld, das den Wert "loading", "success", "empty" oder "error" enthält, und lassen Sie die Darstellung von diesem Wert abhängen, anstatt die Situation aus mehreren unabhängigen Flags wiederherzustellen. In TypeScript macht eine diskriminierte Union unmögliche Kombinationen undarstellbar; der Artikel zu typing useState, useReducer und benutzerdefinierten Hooks behandelt die Typisierungsaspekte ausführlicher.

Checkliste vor dem Erstellen des nächsten Bildschirms

Beantworten Sie diese Fragen, bevor Sie den return-Aufruf der Komponente schreiben:

  1. Was ist sichtbar, bevor Daten ankommen?
  2. Was passiert, wenn die Anfrage fehlschlägt?
  3. Was passiert, wenn sie erfolgreich ist, aber nichts zurückgibt?
  4. Was sieht der Benutzer, während bestehende Daten aktualisiert werden?
  5. Was geschieht bei der Übermittlung eines Formulars?
  6. Was passiert, wenn das Formular zweimal übermittelt wird?
  7. Was geschieht, wenn die Validierung fehlschlägt?

Zusammenfassung

Der Unterschied zwischen einer Demo und einer Produktionsoberfläche liegt selten im „glücklichen“ Ablauf. Er zeigt sich in den Zuständen Laden, leer, Fehler, Neuabrufen und Übermittlung, die sofort auftreten, sobald echte Benutzer und echte Netzwerke involviert sind. Benennen Sie diese Zustände explizit, entwerfen Sie jeden von ihnen sorgfältig, halten Sie Fehler lokal und schützen Sie Benutzereingaben sowie Serverreaktionen vor wiederholten oder fehlgeschlagenen Übermittlungen.