Im Zuge der Entwicklung einer Blog-Anwendung mit React ist es wichtig, eine grundlegende Benutzerauthentifizierung und Validierung zu integrieren. Besonders bei der Registrierung und dem Login werden oft einfache und komplexere Validierungen erforderlich. Diese Validierungen stellen sicher, dass Eingabefelder korrekt ausgefüllt werden und dass Benutzer sich nur mit gültigen Daten anmelden oder registrieren können. Der folgende Abschnitt zeigt, wie man einfache und komplexe Validierungslogiken umsetzt und den Status des Benutzers in verschiedenen Komponenten überträgt.

Ein grundlegendes Beispiel für eine einfache Validierung besteht darin, die HTML-Features zu nutzen. So verhindert etwa die required-Eigenschaft in den Eingabefeldern, dass ein Formular ohne die notwendigen Daten abgesendet wird. Ein Popup informiert den Benutzer darüber, welches Feld noch ausgefüllt werden muss. Eine weitere, komplexere Validierung betrifft die Überprüfung von Passwörtern, bei der sichergestellt wird, dass beide Passwortfelder übereinstimmen. Für solche Anforderungen ist es notwendig, die React-useState-Funktion zu nutzen, um den Fehlerstatus des Formulars zu verfolgen.

Validierung von Benutzerangaben

Um eine einfache Validierung für die Registrierung zu implementieren, können wir wie folgt vorgehen:

  1. Zuerst fügen wir den Eingabefeldern in der Datei src/user/Register.jsx das required-Attribut hinzu. Dies sorgt dafür, dass der Benutzer keine Felder leer lässt.

  2. In der gleichen Datei importieren wir die useState-Funktion von React:

    js
    import { useState } from 'react';
  3. Dann definieren wir einen neuen State-Hook, um den Fehlerstatus für das wiederholte Passwort zu verfolgen:

    js
    const [invalidRepeat, setInvalidRepeat] = useState(false);
  4. In der handleSubmit-Funktion überprüfen wir, ob das Passwort und das wiederholte Passwort übereinstimmen. Falls dies nicht der Fall ist, setzen wir den Fehlerstatus und brechen die Funktion ab:

    js
    if (e.target.elements.password.value !== e.target.elements['password-repeat'].value) {
    setInvalidRepeat(true); return; }
  5. Falls die Passwörter übereinstimmen, setzen wir den Fehlerstatus zurück und fahren mit der Registrierung fort:

    js
    setInvalidRepeat(false);
  6. Am Ende des Formulars fügen wir eine Fehlermeldung hinzu, die angezeigt wird, wenn der Fehlerstatus aktiviert wurde:

    js
    {invalidRepeat && <div>Passwords must match.</div>}

Nach der Implementierung dieser Validierung wird der Benutzer beim Versuch, sich zu registrieren, ohne die Passwörter korrekt zu wiederholen, eine klare Fehlermeldung erhalten. Dies verbessert die Benutzererfahrung und stellt sicher, dass keine ungültigen Daten verarbeitet werden.

Übertragen des Benutzernamens zwischen Komponenten

Ein weiteres wichtiges Element in der App ist die Verwaltung des Benutzernamens, der nach der Anmeldung verwendet wird. Zunächst wird der Benutzername in der UserBar-Komponente hardcodiert, was die Flexibilität der App einschränkt. Um den Benutzernamen über verschiedene Komponenten hinweg zu nutzen, müssen wir den Zustand in die übergeordnete App-Komponente verschieben und den Wert dann an die entsprechenden Komponenten weitergeben.

  1. In der src/user/UserBar.jsx entfernen wir die lokale useState-Deklaration:

    js
    const [username, setUsername] = useState('');
  2. Die UserBar-Komponente wird nun so angepasst, dass sie den Benutzernamen und die setUsername-Funktion als Props erhält:

    js
    export function UserBar({ username, setUsername }) { }
  3. In der src/App.jsx-Datei importieren wir die useState-Funktion und definieren den username-Zustand als globalen Zustand:

    js
    const [username, setUsername] = useState('');
  4. Danach übergeben wir den username-Wert und die setUsername-Funktion an die UserBar-Komponente:

    js
    <UserBar username={username} setUsername={setUsername} />

Dieser Ansatz ermöglicht es, den Benutzernamen global in der App zu verwalten, was besonders dann nützlich ist, wenn er über mehrere Komponenten hinweg benötigt wird.

Beitragsfunktion mit Hooks

Nachdem die Benutzerfunktionalität implementiert wurde, können wir nun die Dynamik der Beitrags-Erstellung in der App einführen. Zunächst müssen wir den Zustand der Posts in der App-Komponente verwalten und diesen an die CreatePost-Komponente weitergeben.

  1. In der src/App.jsx-Datei definieren wir den Zustand für die Posts:

    js
    const [posts, setPosts] = useState(defaultPosts);
  2. Die CreatePost-Komponente erhält nun die setPosts-Funktion als Prop, um neue Posts hinzuzufügen.

  3. In der CreatePost-Komponente definieren wir eine handleSubmit-Funktion, die die Eingabewerte des Benutzers sammelt und den neuen Post in das Post-Array einfügt:

    js
    setPosts((posts) => [newPost, ...posts]);
  4. Nachdem der neue Beitrag hinzugefügt wurde, wird das Formular zurückgesetzt, um die Eingabefelder zu leeren.

Dieser Workflow ermöglicht es Benutzern, neue Beiträge zu erstellen und diese sofort im Feed zu sehen. Da wir den Zustand der Beiträge in der App-Komponente verwalten, können wir die Beiträge effizient über alle Komponenten hinweg verwalten.

Fazit

Die Verwendung von React Hooks für die Verwaltung des Zustands und die Implementierung von Validierungslogiken bietet eine klare und modulare Struktur für komplexe Webanwendungen. In diesem Kapitel haben wir eine Benutzerregistrierung und -anmeldung mit einfacher und komplexer Validierung umgesetzt. Außerdem haben wir gezeigt, wie man den Zustand für den Benutzernamen und die Posts zwischen verschiedenen Komponenten übergibt. Ein wichtiger Aspekt bei der Arbeit mit React ist es, den Zustand so zu organisieren, dass er in den richtigen Komponenten verfügbar ist. Dies sorgt für eine saubere und wartbare Codebasis.

Wie man einen eigenen Theme Hook erstellt: Ein praktischer Leitfaden für React-Anwendungen

Nachdem wir uns im vorherigen Kapitel mit den grundlegenden Konzepten von Hooks vertraut gemacht haben, wollen wir nun unseren eigenen, maßgeschneiderten Hook entwickeln. Dies ermöglicht uns, wiederverwendbare Logik in verschiedenen Komponenten zu abstrahieren, um den Code zu optimieren und die Wartbarkeit zu erhöhen. In diesem Abschnitt werden wir uns darauf konzentrieren, wie man einen benutzerdefinierten Theme Hook erstellt und in einer React-Anwendung verwendet.

Im fünften Kapitel haben wir den ThemeContext eingeführt, um das Styling von Blogbeiträgen in unserer Anwendung zu steuern. Der Zugriff auf den Kontext erfolgte über den eingebauten useContext Hook, was in vielen Komponenten notwendig war. Eine Funktionalität, die mehrfach verwendet wird, ist oft ein guter Kandidat für einen eigenen Hook. So auch hier: Statt in jedem einzelnen Component immer wieder denselben Code zum Abrufen des Themas zu schreiben, können wir diesen Prozess in einen benutzerdefinierten Hook abstrahieren.

Schritt 1: Den benutzerdefinierten Theme Hook erstellen

Um unseren eigenen useTheme Hook zu erstellen, müssen wir zuerst den existierenden Code für den Context Hook in eine separate Funktion extrahieren. Dieser Hook wird das Theme aus dem ThemeContext abrufen und es in der gewünschten Komponente zur Verfügung stellen.

  1. Ordnerstruktur vorbereiten: Kopiere den Ordner Chapter10_3 in einen neuen Ordner Chapter12_1:

    bash
    $ cp -R Chapter10_3 Chapter12_1
  2. Neuen Ordner für Hooks erstellen: Erstelle im src-Verzeichnis einen neuen Ordner hooks.

  3. Theme Hook Datei erstellen: Erstelle die Datei src/hooks/theme.js und importiere den useContext Hook sowie den ThemeContext:

    javascript
    import { useContext } from 'react';
    import { ThemeContext } from '@/contexts/ThemeContext.js';
  4. useTheme Funktion definieren: Erstelle den useTheme Hook, der den Wert des Themes zurückgibt:

    javascript
    export function useTheme() { return useContext(ThemeContext); }

Das war schon alles! Wir haben nun einen eigenen Hook erstellt, der die Funktionalität des Kontextes abstrahiert.

Schritt 2: Den Theme Hook in der Anwendung verwenden

Nun, da unser Theme Hook erstellt wurde, wollen wir ihn in der Anwendung nutzen und die bestehenden Context-Abfragen durch unseren neuen Hook ersetzen.

  1. Post-Komponente anpassen: Öffne die Datei src/components/post/Post.jsx und entferne die Importe für useContext und ThemeContext. Ersetze sie durch den Import des neuen useTheme Hooks:

    javascript
    import { useTheme } from '@/hooks/theme.js';

    Ändere dann den Code in der Komponente, um den useTheme Hook zu verwenden:

    javascript
    export function Post({ id }) { const theme = useTheme(); }
  2. PostListItem-Komponente anpassen: Wiederhole den gleichen Vorgang für die Datei src/components/post/PostListItem.jsx, indem du die Importe ersetzt und den Hook verwendest:

    javascript
    export function PostListItem({ id, title, author }) {
    const theme = useTheme(); }
  3. Entwicklungsserver starten: Führe den Entwicklungsserver erneut aus:

    bash
    $ npm run dev

    Du wirst feststellen, dass das Theme wie gewohnt funktioniert, aber der Code ist nun etwas vereinfacht und flexibler. Ein zusätzlicher Vorteil ist, dass wir das Thema später problemlos ändern können. Wenn wir beispielsweise das Thema aus einer Benutzereinstellung statt aus dem Kontext laden möchten, können wir diese Änderung im useTheme Hook vornehmen, und alle Komponenten, die diesen Hook verwenden, profitieren automatisch von der Änderung.

Was ist noch zu beachten?

Es ist wichtig zu verstehen, dass der Hauptvorteil eines benutzerdefinierten Hooks darin besteht, dass er die Wiederverwendbarkeit und Wartbarkeit des Codes erhöht. In diesem Fall haben wir den useTheme Hook erstellt, um die Logik zur Verwaltung des Themas zentral zu steuern, was uns erlaubt, den Code in den einzelnen Komponenten zu vereinfachen. Dies sorgt für eine klare Trennung von Logik und Präsentation, da alle Komponenten, die das Thema benötigen, einfach den Hook verwenden und sich nicht mehr um den direkten Zugriff auf den Kontext kümmern müssen.

Ein weiterer wichtiger Punkt ist, dass der benutzerdefinierte Hook in Zukunft leicht erweitert werden kann. Wenn sich die Art und Weise, wie wir das Thema handhaben, ändert (z. B. wenn wir zusätzliche Konfigurationen wie eine benutzerdefinierte Farbpalette oder benutzerspezifische Einstellungen integrieren möchten), müssen wir nur den useTheme Hook anpassen, ohne alle betroffenen Komponenten zu ändern.

Zusätzlich könnte dieser Hook später auch mit einer globalen State-Management-Lösung wie Redux oder Zustand kombiniert werden, wenn die Anforderungen an das Projekt wachsen und komplexer werden. So lässt sich der Hook mühelos an eine größere Architektur anpassen, ohne die gesamte Struktur der Anwendung zu beeinträchtigen.

Es sollte außerdem beachtet werden, dass die Entwicklung benutzerdefinierter Hooks immer auch die richtige Balance zwischen Abstraktion und Verständlichkeit erfordert. Wenn ein Hook zu viele Verantwortlichkeiten übernimmt oder zu komplex wird, könnte dies die Lesbarkeit und Wartbarkeit des Codes beeinträchtigen. Daher ist es ratsam, die Verantwortung eines Hooks klar und fokussiert zu halten, um den Code so modular und übersichtlich wie möglich zu gestalten.

Wie man benutzerdefinierte Hooks in React erstellt und verwendet: Refactoring von Komponenten

In modernen Webanwendungen wird die Verwendung von benutzerdefinierten Hooks zunehmend populär, um die Lesbarkeit und Wartbarkeit des Codes zu verbessern. Dies gilt insbesondere für Anwendungen, die komplexe Logiken und wiederverwendbare Funktionen wie das Abrufen von API-Daten, das Verwalten von Zuständen oder das Implementieren von Undo/Redo-Mechanismen erfordern. In dieser Sektion zeigen wir, wie benutzerdefinierte Hooks die Struktur einer React-Anwendung optimieren und zu einer klareren Trennung von Logik und UI führen.

Ein zentrales Konzept bei der Arbeit mit Hooks ist, dass diese es ermöglichen, Geschäftslogik aus Komponenten herauszulösen und wiederverwendbare Funktionen zu schaffen. Dies hilft nicht nur dabei, Komponenten übersichtlicher und leichter verständlich zu machen, sondern auch, den Entwicklungsprozess zu beschleunigen, da viele Aufgaben durch einmal definierte Hooks zentralisiert und wiederverwendet werden können. Nehmen wir an, wir arbeiten mit einer Blog-Anwendung, die Posts anzeigt, erstellt und bearbeitet. Um die Wiederverwendbarkeit und Lesbarkeit zu verbessern, können wir in verschiedenen Komponenten benutzerdefinierte Hooks einsetzen.

Zu Beginn werden wir sehen, wie die bestehende API-Abfrage und das Erstellen von Posts mit benutzerdefinierten Hooks refaktoriert werden. Dies betrifft insbesondere das Ersetzen von Standard-API-Calls durch eigene, spezifische Hooks, die eine klarere Trennung der Zuständigkeitsbereiche ermöglichen. Hierfür nutzen wir den useAPIFetchPost-Hook, um Beiträge zu laden, und den useAPICreatePost-Hook, um neue Posts zu erstellen.

Ein typisches Beispiel ist der Post-Komponenten-Code. Zunächst verwenden wir die React-Query-Methode useSuspenseQuery, um Daten zu laden. Diese wird durch den benutzerdefinierten useAPIFetchPost-Hook ersetzt. Dieser neue Hook kapselt die Logik des Datenabrufs und ermöglicht es, sich auf die Benutzerinteraktionslogik zu konzentrieren, ohne sich um die Details der API-Kommunikation kümmern zu müssen.

Der nächste Schritt ist die Umstellung des Codes in der PostSearchResults-Komponente, um den useAPISearchPosts-Hook zu verwenden, der die Logik zur Suche nach Posts zentralisiert. Dies macht die PostSearchResults-Komponente erheblich einfacher, da sie nur noch mit der Logik zur Darstellung von Ergebnissen befasst ist, während die API-Abfrage intern im Hook abgewickelt wird.

Ein weiteres Beispiel ist die CreatePost-Komponente. Ursprünglich wurde hier der useMutation-Hook von React Query verwendet, um einen neuen Beitrag zu erstellen. Durch den benutzerdefinierten useAPICreatePost-Hook können wir diese Logik in eine zentrale Funktion auslagern, die das Erstellen von Posts vereinfacht und die Komponente übersichtlicher macht. Nach der Implementierung des Hooks wird die Logik zum Erstellen eines Beitrags wie folgt durchgeführt:

js
const newPost = { title, content, author: username, featured: false };
try { const result = await createPost(newPost); clear(); navigate(`/post/${result.id}`); } catch (err) { return err; }

Diese Vorgehensweise ermöglicht es, den Zustand und die API-Aufrufe in einer separaten Hook zu kapseln, wodurch die Komponenten erheblich vereinfacht und gleichzeitig ihre Funktionalität erhalten bleibt.

Nachdem wir nun einfache benutzerdefinierte Hooks implementiert haben, wollen wir uns einer etwas komplexeren Herausforderung zuwenden: der Erstellung eines Debounced History State Hooks. Dieser Hook bietet die Möglichkeit, eine Undo/Redo-Funktionalität mit einer Debounce-Logik zu kombinieren. Ein solcher Hook ist nützlich, um das Speichern von Historie-Änderungen zu optimieren und unnötige Zwischenzustände zu vermeiden, die die Performance der Anwendung beeinträchtigen könnten.

Der useDebouncedHistoryState-Hook kombiniert mehrere Funktionalitäten, darunter das Verwalten von Undo/Redo und das Debouncen von Änderungen. Dieser Hook wird durch die Kombination von useState, useDebouncedCallback und useHistoryState erstellt. Der Hauptvorteil dieses Ansatzes liegt in der Abstraktion der Logik, sodass die CreatePost-Komponente nicht mehr mit der Verwaltung von Historie und Debounce-Logik überladen wird.

Hier ist ein Beispiel, wie dieser Hook in der CreatePost-Komponente verwendet wird:

js
const { content, handleContentChange, undo, redo, clear, canUndo, canRedo } = useDebouncedHistoryState('', 200);

Durch diese Änderung wird die CreatePost-Komponente erheblich vereinfacht, da sie sich nur noch um die Benutzerinteraktion und nicht um die Verwaltung von Historienzuständen kümmert. So bleibt der Code sauber und fokussiert, was das Verständnis und die Wartung der Anwendung erleichtert.

Ein weiterer wichtiger Aspekt bei der Arbeit mit benutzerdefinierten Hooks ist die Notwendigkeit, diese zu testen, um sicherzustellen, dass sie auch bei späteren Änderungen zuverlässig funktionieren. Dafür können wir Vitest einsetzen, eine moderne Testing-Lösung, die hervorragend mit Vite und React zusammenarbeitet. Mit Vitest lassen sich schnelle und effiziente Tests für unsere benutzerdefinierten Hooks schreiben, ohne dass eine zusätzliche Komponentenstruktur notwendig ist. So kann sichergestellt werden, dass jeder Hook unter den gegebenen Bedingungen korrekt funktioniert, was die Stabilität der Anwendung erhöht.

Die Verwendung von benutzerdefinierten Hooks bietet also viele Vorteile: eine klare Trennung der Logik, eine höhere Wiederverwendbarkeit und eine verbesserte Lesbarkeit des Codes. Indem wir die Geschäftslogik aus den Komponenten herausnehmen und in spezialisierte Hooks verlagern, machen wir unsere React-Anwendung nicht nur leichter wartbar, sondern auch flexibler und besser skalierbar.