In React werden Hooks verwendet, um den Zustand in Funktionskomponenten zu verwalten und neben anderen nützlichen Funktionen den Code zu vereinfachen. Bei der Implementierung eines eigenen Hooks oder der Nutzung von Reacts eingebauten Hooks ist es jedoch wichtig, die Regeln zu verstehen, die bestimmen, wie und wann Hooks aufgerufen werden können. Eine der zentralen Regeln von React lautet, dass Hooks niemals bedingt aufgerufen werden dürfen. Dies führt uns zu einer wichtigen Erkenntnis: Die Reihenfolge, in der Hooks in einer Komponente definiert werden, ist von entscheidender Bedeutung.
Ein klassisches Beispiel für die Funktionsweise von Hooks zeigt sich in der Reimplementation des useState Hooks. Wenn wir einen Zustand definieren und ihn später in einer Komponente aktualisieren möchten, setzen wir in einer eigenen Hook-Implementierung ein globales Array ein, um die Werte zu speichern. Diese Speicherung erfolgt unter der Bedingung, dass der Hook bei jeder erneuten Ausführung in der gleichen Reihenfolge aufgerufen wird. Das bedeutet, dass wir den aktuellen Index (currentHook) als Parameter an das Array weitergeben und diesen Index nach der Ausführung der Funktion inkrementieren. Dies stellt sicher, dass der Zustand des jeweiligen Hooks nach der Rückkehr aus der Funktion korrekt aktualisiert wird.
Jedoch gibt es Situationen, in denen man den Zustand abhängig von einer Bedingung definieren möchte, wie etwa bei der Implementierung eines Kontrollkästchens, das die Verwendung des Namensfeldes aktivieren oder deaktivieren kann. In diesem Fall könnte man die folgende Syntax verwenden, um den Zustand des Namensfeldes bedingt zu steuern:
Dies führt jedoch zu einem Problem: ESLint gibt uns eine Warnung, da React Hooks nicht bedingt aufgerufen werden dürfen. Um diese Warnung zu umgehen, deaktiviert man diese Regel, was jedoch zu einer fehlerhaften Funktionsweise führen kann. Der Grund für den Fehler ist, dass sich die Reihenfolge der Hooks ändert, wenn man bedingt einen neuen Hook einfügt. React ordnet jedem Hook eine Position im internen Hook-Array zu, basierend auf der Reihenfolge, in der sie aufgerufen werden. Wenn die Reihenfolge der Hooks durch bedingte Aufrufe verändert wird, überschreibt der neue Hook die Position eines bereits existierenden Hooks, was zu einem Zustand führen kann, in dem Daten nicht mehr korrekt verarbeitet werden.
Dies ist besonders problematisch, wenn der neue Hook in die Mitte des bestehenden Codes eingefügt wird, was dazu führen kann, dass der Zustand eines anderen Hooks überschrieben wird. Wenn etwa der Name- und Nachname-Hook existieren und ein neuer Hook für das Kontrollkästchen eingeführt wird, verschiebt sich die Reihenfolge der Hooks, was dazu führt, dass der Name-Hook plötzlich die Position des Nachname-Hooks einnimmt und somit die Zustände durcheinander geraten. Dieser Fehler kann dazu führen, dass der Wert des Nachnamens im Namensfeld angezeigt wird und das Nachname-Feld leer bleibt, was genau das Gegenteil des beabsichtigten Verhaltens ist.
Die Regel, dass Hooks in der gleichen Reihenfolge aufgerufen werden müssen, ist also nicht nur eine technische Einschränkung, sondern schützt die Integrität des Zustandsmanagements in React-Komponenten. Würde man diese Regel umgehen, könnten unvorhersehbare Fehler auftreten, die schwer zu debuggen sind.
Im weiteren Verlauf lernen wir, dass React-Hooks nicht nur eine einfache Möglichkeit bieten, den Zustand zu verwalten, sondern auch an bestimmte Bedingungen gebunden sind. Diese Bedingungen sind entscheidend, um eine konsistente und fehlerfreie Nutzung der Hooks zu gewährleisten. So dürfen Hooks nicht nur nicht bedingt, sondern auch nicht in Schleifen, innerhalb von Event-Handlern, oder innerhalb von try/catch/finally-Blöcken aufgerufen werden. Auch in Funktionen, die an andere Hooks wie useMemo, useEffect oder useReducer übergeben werden, sind Hooks nicht erlaubt. Diese Einschränkungen stellen sicher, dass die Reihenfolge der Hooks immer konstant bleibt und nicht durch externe Faktoren beeinflusst wird.
Eine häufige Frage, die in diesem Zusammenhang aufkommt, ist, warum React diese Einschränkungen eingeführt hat. Warum kann man Hooks nicht bedingt aufrufen? Der Hauptgrund dafür liegt in der Funktionsweise von Reacts internem Hook-Management. React verwendet intern ein Array, um die Hooks in der Reihenfolge ihres Aufrufs zu speichern. Wenn diese Reihenfolge durch bedingte Aufrufe verändert wird, kann React nicht mehr korrekt verfolgen, welchem Zustand welcher Hook zugeordnet ist. Das führt zu den bereits beschriebenen Problemen.
Alternativen zur bedingten Verwendung von Hooks wären theoretisch denkbar, wie etwa die Benennung von Hooks und deren Speicherung in einem Objekt anstelle eines Arrays. Dies würde jedoch eine komplizierte API mit sich bringen, da jedes Hook einen eindeutigen Namen erfordern würde, was wiederum die Flexibilität und Lesbarkeit des Codes beeinträchtigen würde. Auch diese Lösung hätte ihre eigenen Probleme, da es schwieriger wäre, mit solchen benannten Hooks zu arbeiten und Konflikte zwischen den Namen zu vermeiden.
Ein weiteres Beispiel für ein solches alternatives API könnte die Verwendung von useState sein, wobei der Zustand nicht in einem Array, sondern in einem benannten Objekt gespeichert wird. Auch wenn dies technisch möglich wäre, würde die Komplexität des Codes deutlich steigen und die intuitivere Nutzung von React verlieren.
Die aktuellen Einschränkungen von React Hooks bieten also eine notwendige Struktur, die sicherstellt, dass der Zustand korrekt verwaltet wird. Dies bedeutet jedoch nicht, dass es keine Alternativen gibt. In den kommenden Kapiteln werden wir uns andere mögliche Ansätze ansehen, wie etwa die Verwendung von benutzerdefinierten Hooks oder anderen API-Methoden, um ähnliche Funktionalitäten zu erreichen, ohne die Regeln von React zu verletzen.
Wie man Benutzerkomponenten und Blog-Posts in React erstellt und verwaltet
Im Kontext von React-Anwendungen ist es entscheidend, eine benutzerfreundliche und dynamische Benutzeroberfläche zu schaffen. Ein besonders häufiges Szenario ist die Verwaltung von Benutzeraktionen wie dem Einloggen, Ausloggen sowie das Erstellen und Anzeigen von Blog-Posts. In dieser Kapitel werden verschiedene Komponenten erläutert, die diesen Anforderungen gerecht werden.
Die erste Aufgabe ist es, eine Registrieren-Komponente zu erstellen. Diese Komponente ist fast identisch mit der Login-Komponente, unterscheidet sich jedoch durch ein zusätzliches Eingabefeld und einen anderen Text auf dem Button. Die Registrieren-Komponente wird dann in der Hauptkomponente der Anwendung (App.jsx) eingebaut, um der Anwendung die Funktionalität zu verleihen, neue Benutzer zu registrieren.
Danach folgt die Logout-Komponente. Diese Komponente wird die Möglichkeit bieten, sich abzumelden, indem sie den Namen des derzeit eingeloggenen Benutzers anzeigt und einen Button zum Abmelden bereitstellt. Hier wird die username-Eigenschaft als Prop an die Logout-Komponente übergeben, wobei diese in der Komponente zur Anzeige des Benutzernamens verwendet wird. Die Logout-Komponente rendert einen einfachen Button, der beim Klicken den Benutzer abmeldet.
Nach der Erstellung der beiden oben genannten Komponenten wird der UserBar entwickelt. Diese Komponente zeigt je nach Zustand der Benutzeranmeldung entweder die Login- und Registrieren-Komponenten oder die Logout-Komponente an. Die Verwendung von React Fragments (shorthand: <> </>) sorgt dafür, dass die Benutzeroberfläche nicht durch unnötige HTML-Elemente verschmutzt wird und die Komponenten nebeneinander gerendert werden.
In der nächsten Phase wird eine dynamische Benutzerverwaltung mit Hooks eingeführt, sodass die Benutzerinteraktionen wie das Einloggen und Abmelden ohne manuelles Bearbeiten des Codes stattfinden können. Dies ermöglicht es, den Zustand der Anwendung auf einfache Weise zu ändern, sobald der Benutzer eingeloggt ist.
Sobald die Benutzerkomponenten implementiert sind, wird das nächste Ziel darin bestehen, eine Post-Komponente zu erstellen, die es den Benutzern ermöglicht, Blog-Beiträge anzuzeigen. Ein einzelner Beitrag besteht dabei aus einem Titel, Inhalt und dem Autor (dem Benutzer, der den Beitrag geschrieben hat).
Um den Prozess des Erstellens von neuen Blog-Beiträgen zu ermöglichen, wird die CreatePost-Komponente entwickelt. Diese Komponente zeigt ein Formular an, in dem der Benutzer den Titel und den Inhalt seines Beitrags eingeben kann. Der Name des Autors wird dabei automatisch mit dem Benutzernamen des eingeloggenen Nutzers gefüllt. Die PostList-Komponente schließlich wird alle erstellten Beiträge anzeigen, wobei jeder Beitrag durch die Verwendung der map-Funktion als eigenes Post-Element gerendert wird.
Die Komplexität der Post-Komponenten ist im Vergleich zu den Benutzerkomponenten relativ einfach, aber dennoch sehr wichtig für die Funktionalität einer Blog-Anwendung. Während Post nur die Darstellung eines einzelnen Beitrags übernimmt, sorgt PostList dafür, dass alle Beiträge angezeigt werden.
Für die optimale Nutzung dieser Komponenten ist es entscheidend, den Zustand der Anwendung mit React Hooks zu verwalten. So bleibt der Zustand der Anwendung immer aktuell, und der Benutzer kann in Echtzeit mit der Anwendung interagieren, ohne dass die Seite neu geladen werden muss. Dies ist einer der wesentlichen Vorteile von React, der den Entwicklern eine hohe Flexibilität bietet.
Es ist wichtig zu beachten, dass alle Komponenten modular und wiederverwendbar aufgebaut sind. Dies erleichtert nicht nur die Wartung und Erweiterung der Anwendung, sondern stellt auch sicher, dass neue Funktionen problemlos hinzugefügt werden können, ohne dass bestehende Funktionalitäten beeinträchtigt werden. Der Code bleibt sauber und übersichtlich, was die Skalierbarkeit der Anwendung fördert.
Die Verwendung von Props und State innerhalb der Komponenten erfordert ein gutes Verständnis von Reacts Funktionsweise. Insbesondere ist es wichtig, beim Weiterreichen von Daten zwischen den Komponenten sicherzustellen, dass alle erforderlichen Informationen korrekt übergeben werden. Auch das Konzept der Controlled Components ist von Bedeutung, wenn Formulare wie das für die Erstellung von Blog-Beiträgen genutzt werden.
Die Implementierung dieser grundlegenden Funktionalitäten legt den Grundstein für eine vollwertige Blog-Plattform. Bei der Weiterentwicklung der Anwendung kann die Benutzererfahrung durch Features wie das Hinzufügen von Kommentaren, das Bearbeiten von Beiträgen oder die Implementierung einer Datenbank zur Speicherung von Benutzerdaten und Posts weiter verbessert werden.
Wie man React Contexts implementiert und optimal nutzt
Die Verwendung von React Contexts kann die Art und Weise, wie Daten in einer React-Anwendung weitergegeben werden, erheblich verbessern. Statt props über mehrere Komponenten hinweg weiterzureichen, ermöglicht der Context eine zentrale Verwaltung und Übergabe von Daten. Doch wie implementiert man React Contexts korrekt und wann sollte man auf alternative Lösungen zurückgreifen?
Ein einfaches Beispiel für die Implementierung eines Contexts zeigt, wie die Nutzung eines Context Hook die direkte Verwendung von Werten aus dem Context ermöglicht. Statt wie zuvor eine Wrapper-Komponente zu benötigen, kann nun direkt auf den Context-Wert zugegriffen und dieser genutzt werden, um eine Komponente zu rendern. Dies reduziert die Komplexität und macht den Code übersichtlicher. In diesem Zusammenhang ist auch das Konzept der "Provider" von Bedeutung. Wird kein Provider definiert, verwendet der Context den Standardwert, der bei der Erstellung des Contexts festgelegt wurde. Dieser Standardwert wird verwendet, falls der Context nicht in eine hierarchische Struktur eingebunden ist, wie es beispielsweise bei interaktiven Style-Guides wie Storybook der Fall sein kann.
Wird jedoch ein Provider definiert, so überschreibt dieser den Standardwert und ermöglicht eine dynamische Veränderung der Context-Werte. Wenn beispielsweise der Wert des Contexts verändert wird, wird der Wert des Contexts für alle untergeordneten Komponenten im Baum aktualisiert. Dies kann nützlich sein, um Zustände wie das Design-Thema (z. B. die Farbe von Titeln) in einer Anwendung global zu steuern.
Wichtig zu beachten ist, dass, wenn kein Wert an den Provider übergeben wird, der Wert des Contexts auf undefined gesetzt wird. Dies bedeutet, dass der Context in einem solchen Fall keine nützlichen Daten liefert. Werden jedoch mehrere Provider für denselben Context verwendet, so wird der Wert des Contexts für die Komponenten, die tiefer im Baum verschachtelt sind, entsprechend angepasst. In einem Beispiel, bei dem zwei Provider verwendet werden, wird der Kontextwert je nach Nähe zum jeweiligen Provider überschrieben.
Diese Technik, mehrere Provider zu verschachteln, ist besonders nützlich, wenn Teile einer Anwendung unterschiedliche Werte für denselben Context benötigen. Beispielsweise könnte man in einer Blog-Anwendung einen "Featured Posts"-Bereich implementieren, der eine andere Farbgebung als die restlichen Beiträge verwendet. Hierfür würde man den Context für diesen Bereich überschreiben, ohne die anderen Teile der Anwendung zu beeinflussen.
Es gibt jedoch einige Fallstricke bei der Verwendung von React Context. Ein häufiger Fehler ist es, Context zu übermäßig zu verwenden. Wenn der Context für ständig wechselnde Daten genutzt wird, kann dies dazu führen, dass die gesamte Komponentenhierarchie unnötig oft neu gerendert wird, was die Leistung der Anwendung erheblich beeinträchtigen kann. Besonders häufig ändernde Werte, wie etwa Zustände, die auf vielen Ebenen benötigt werden, sollten besser mit Lösungen wie Jotai, Redux oder MobX verwaltet werden. Diese State-Management-Tools bieten eine feinere Kontrolle und ermöglichen es, nur die Teile des Zustands zu aktualisieren, die tatsächlich betroffen sind, wodurch unnötige Renderings vermieden werden.
Kontext ist jedoch besonders effektiv für weniger häufig ändernde Daten wie Themenwechsel (z. B. für eine "Dark Mode"-Funktionalität) oder Internationalisierungsdaten (i18n). Für solche globalen Zustände bietet der Context eine einfache Möglichkeit, diese Daten über die gesamte Anwendung hinweg zugänglich zu machen.
Ein weiteres Beispiel für die Verwendung von Contexts ist die Übergabe von Daten durch mehrere Komponenten hinweg ohne die Notwendigkeit, Props manuell durch jede Komponente zu schieben. Nehmen wir an, eine Page-Komponente hat eine headerSize-Prop, die in einer tief verschachtelten Avatar-Komponente benötigt wird. Anstatt die headerSize-Prop durch jede Ebene zu leiten, kann der Context genutzt werden, um diese Prop an alle Komponenten weiterzugeben, die sie benötigen. Auf diese Weise kann der Code eleganter und wartungsfreundlicher gestaltet werden.
Wichtig für den Leser ist, dass React Context in den richtigen Szenarien verwendet werden sollte. Zu häufiges oder falsches Verwenden kann zu Problemen führen, sowohl in Bezug auf die Leistung als auch auf die Code-Wartbarkeit. Context ist besonders nützlich für Daten, die global sind und sich nicht ständig ändern. Wenn die Daten jedoch sehr dynamisch sind, kann es besser sein, auf spezialisierte State-Management-Lösungen zurückzugreifen, die eine präzisere Kontrolle ermöglichen. Nur durch das richtige Verständnis und den gezielten Einsatz von Context können die Vorteile dieser Methode optimal ausgeschöpft werden.
Wie man eine dynamische Todo-App in React mit StateContext implementiert
In diesem Abschnitt wird die Implementierung einer dynamischen Todo-App in React behandelt, die mithilfe des StateContext und verschiedener Funktionen die Verwaltung von Todo-Elementen ermöglicht. Dabei werden grundlegende Konzepte wie das Erstellen, Abrufen, Hinzufügen, Umschalten, Filtern und Entfernen von Todo-Elementen behandelt, sowie das Verwalten des Status mit Hilfe von React's Context API.
Zunächst wird ein StateContext eingerichtet, um den Zustand der Todo-App zu speichern. Hierzu wird eine neue Datei StateContext.js erstellt, die die createContext-Funktion von React importiert. Dieser Context wird mit einem leeren Array initialisiert, das später die Todo-Elemente enthalten wird:
Der App-Komponent wird anschließend dynamisch gestaltet. Dies beginnt mit der Implementierung von Funktionen, die es ermöglichen, Todo-Elemente zu laden, hinzuzufügen, zu toggeln, zu filtern und zu entfernen. Zunächst wird die App.jsx Datei bearbeitet, in der der StateContext und eine Funktion zum Abrufen der Todo-Daten aus einer API importiert werden:
Die App-Komponente erhält einen Konstruktor, der den Initialzustand festlegt. Anfangs gibt es noch keine Todo-Elemente, und der Filter ist auf "alle" gesetzt:
Der componentDidMount Lifecycle-Methoden wird verwendet, um die Todo-Daten beim ersten Laden der Komponente zu laden:
Die loadTodos-Methode lädt die Todo-Daten und ruft anschließend die filterTodos-Methode auf, um die angezeigten Todo-Elemente zu filtern:
Die Funktion addTodo erstellt ein neues Todo-Element, fügt es zum Array hinzu und filtert anschließend die angezeigten Todos:
Mit der toggleTodo-Methode kann der Status eines Todo-Elements zwischen "abgeschlossen" und "nicht abgeschlossen" umgeschaltet werden:
Das Entfernen eines Todo-Elements erfolgt mit der removeTodo-Methode, die das entsprechende Element aus dem Array entfernt:
Die Filterung der Todo-Elemente wird durch die applyFilter-Methode ermöglicht, die verschiedene Filter wie "aktiv", "abgeschlossen" oder "alle" anwendet:
Mit der filterTodos-Methode wird der Zustand basierend auf dem angewendeten Filter aktualisiert:
Um die Methoden korrekt an die Komponenten weiterzugeben, muss der this-Kontext im Konstruktor neu gebunden werden:
Nachdem die App-Komponente dynamisch gemacht wurde, geht es darum, auch die anderen Komponenten, wie die AddTodo-Komponente, dynamisch zu gestalten. Hier wird ein Zustand für das Eingabefeld definiert und Methoden zum Bearbeiten des Eingabewerts sowie zum Absenden des Formulars erstellt. Im Konstruktor wird der this-Kontext ebenfalls neu gebunden:
Die Methode handleInput behandelt Änderungen im Eingabefeld, während handleSubmit das Hinzufügen eines neuen Todo-Elements steuert. Wenn das Eingabefeld leer ist, wird der Absende-Button deaktiviert:
Der render-Teil der AddTodo-Komponente stellt sicher, dass der Eingabewert korrekt angezeigt wird und die Handhabung des Formulars funktioniert.
Wichtige Aspekte:
Neben der Implementierung der grundlegenden Logik für das Hinzufügen, Löschen und Filtern von Todo-Elementen sollte der Leser berücksichtigen, dass die Verwendung von React-Klassenkomponenten und deren Lifecycle-Methoden wie componentDidMount und setState einen klassischen Ansatz darstellen, der sich von den neueren Funktionskomponenten und Hooks unterscheidet. Während Klassenkomponenten nach wie vor weit verbreitet sind, empfiehlt es sich, für neue Projekte auf Funktionskomponenten mit Hooks umzusteigen, da diese eine einfachere und elegantere Handhabung des Zustands und der Nebenwirkungen ermöglichen. Ebenso ist der Umgang mit dem this-Kontext ein häufiger Stolperstein bei Klassenkomponenten, da er korrekt gebunden werden muss, um Fehler zu vermeiden.

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский