7.4 React-Grundlagen: State und der Render-Kreislauf
Nach dieser Einheit kannst du:
- erklären, warum eine lokale Variable sich nicht als Zustand eignet, und Zustand mit
useStateverwalten. - State nur über den Setter und Arrays und Objekte immutabel aktualisieren, mit der Funktionsform, wenn der neue Wert vom alten abhängt.
- die Regeln für Hooks nennen und begründen.
- den Render-Kreislauf beschreiben und entscheiden, ob ein Wert State ist oder beim Rendern berechnet wird.
Ein Kellner mit dem Gedächtnis eines Goldfischs: Nach jeder Runde durchs Lokal hat er alles vergessen. Damit trotzdem keine Bestellung verloren geht, liegt an der Theke ein Notizbuch. Bevor er losgeht, schaut er hinein; ändert sich etwas, trägt er es dort ein und dreht eine neue Runde. Genau so arbeitet eine React-Komponente: Bei jedem Rendern läuft die Funktion von vorn und vergisst alle lokalen Variablen. Das Notizbuch führt React, und der Eintrag darin heißt State.
flowchart LR
accTitle: React bewahrt den State außerhalb der Funktion auf
react[("React<br/>Notizbuch: count = 3")] -- "useState liefert 3" --> run["Komponente<br/>läuft von vorn"]
run -- "setCount(4)" --> react
run --> ui["Anzeige: 3"]
Abb. 7.14: Die Funktion vergisst bei jedem Durchlauf alles; den State bewahrt React auf (eigene Darstellung).
Props kommen von außen (Einheit 7.3); State ist der Zustand, den eine Komponente selbst besitzt und ändert. Er ist nötig, sobald sich etwas durch Benutzung ändert: der Text im Eingabefeld, das gewählte Preset, der Inhalt des Warenkorbs. Diese Einheit führt useState ein, den ersten Hook, und zeigt den Kreislauf, in dem jede React-Anwendung arbeitet.
Begriffe vorab
Abschnitt betitelt „Begriffe vorab“- State: Zustand, den eine Komponente selbst besitzt und der beim Ändern ein neues Rendern auslöst.
- Rendern: React führt die Komponentenfunktion aus und erhält das JSX für den aktuellen Stand.
- Setter: die Funktion aus
useState, über die der State geändert wird, etwasetCount. - Immutabel aktualisieren: ein Array oder Objekt nicht verändern, sondern durch eine geänderte Kopie ersetzen.
- Hook: eine Funktion mit
use-Präfix, über die Komponenten React-Mechanismen wie State nutzen.
Warum eine normale Variable nicht reicht
Abschnitt betitelt „Warum eine normale Variable nicht reicht“Eine Auswahl, die beim Klick auf “Next preset” das nächste Render-Preset zeigen soll. Der Versuch mit einer gewöhnlichen Variablen, und warum er nicht funktioniert:
const presets = ["web-1080p", "web-4k", "mobile-720p"];
export function PresetPicker() { let index = 0; // a plain local variable
function handleClick() { index = (index + 1) % presets.length; // changes the variable ... }
return ( <> <button onClick={handleClick}>Next preset</button> <p>Preset: {presets[index]}</p> </> );}Beispielprojekt: 07_react_grundlagen_examples/ex12_plain_variable
Der Klick ändert index tatsächlich, sichtbar wird davon nichts. Zwei Gründe:
- Lokale Variablen überleben das Rendern nicht. Rendert React die Komponente erneut, läuft die Funktion von vorn, und
indexstartet wieder bei0. - Die Änderung löst kein Rendern aus. React erfährt nichts davon und hat keinen Grund, die Anzeige zu erneuern.
sequenceDiagram
accTitle: Eine lokale Variable überlebt das Rendern nicht
participant user as Benutzer
participant comp as PresetPicker
participant react as React
react->>comp: Rendern: index = 0
user->>comp: Klick
comp->>comp: index = 1, kein Rendern
react->>comp: späteres Rendern: index = 0
Abb. 7.15: Die Änderung bleibt unsichtbar und ist beim nächsten Rendern wieder weg (eigene Darstellung).
Damit eine Änderung sichtbar wird, braucht es also beides: Der Wert muss erhalten bleiben, und das Rendern muss ausgelöst werden. Genau dafür gibt es useState.
useState
Abschnitt betitelt „useState“import { useState } from "react";
export function PresetPicker() { const [index, setIndex] = useState(0); // state variable + setter
function handleClick() { setIndex((current) => (current + 1) % presets.length); }
return ( <> <button onClick={handleClick}>Next preset</button> <p>Preset: {presets[index]}</p> </> );}Beispielprojekt: 07_react_grundlagen_examples/ex13_use_state
useState(startwert) liefert ein Array mit genau zwei Einträgen, das per Destructuring entpackt wird: den aktuellen Wert und eine Setter-Funktion. Die Namenskonvention ist [something, setSomething]. Den Typ leitet TypeScript aus dem Startwert ab (useState(0) ergibt number); nur wenn der Startwert nichts verrät, gibst du ihn an: useState<Job[]>([]) oder useState<Job | null>(null).
sequenceDiagram
accTitle: useState über zwei Renderdurchgänge
participant comp as PresetPicker
participant react as React
comp->>react: 1. Rendern: useState(0)
react-->>comp: [0, setIndex]
Note over comp: Klick
comp->>react: setIndex(1)
Note over react: merkt sich 1, plant neues Rendern
comp->>react: 2. Rendern: useState(0)
react-->>comp: [1, setIndex]
Abb. 7.16: Der Startwert zählt nur beim ersten Rendern; danach liefert React den gemerkten Wert (eigene Darstellung).
So arbeitet das zusammen:
- Erstes Rendern.
useState(0)liefert[0, setIndex]. React merkt sich: Der Wert ist0. - Aktion. Der Klick ruft
setIndex(...). React notiert den neuen Wert1und plant ein neues Rendern. - Zweites Rendern. Im Code steht weiterhin
useState(0), React weiß aber, dass der Wert inzwischen1ist, und liefert[1, setIndex].
Der Startwert wird also nur beim ersten Rendern verwendet; danach gewinnt das, was React sich gemerkt hat.
Die drei Regeln für State
Abschnitt betitelt „Die drei Regeln für State“- Zustand wird nie direkt geändert, nur über den Setter.
index = 2bewirkt nichts Sichtbares; erstsetIndex(2)löst das Neu-Rendern aus. - Objekte und Arrays werden ersetzt, nicht verändert. React erkennt Änderungen daran, dass ein anderes Objekt im State steht;
list.push(item)bleibt unsichtbar, weil das Array dasselbe bleibt. Richtig ist die Kopie mit Spread:setJobs([...jobs, newJob])beziehungsweisesetJob({ ...job, state: "done" }). - Folgt der neue Wert aus dem alten, nimmt der Setter eine Funktion:
setCount((current) => current + 1).
Zur dritten Regel gehört ein Beispiel, weil sie sonst wie Geschmackssache wirkt:
// two increments in one handler:setCount(count + 1);setCount(count + 1); // count is the value of THIS render: +1 in total
setCount((c) => c + 1);setCount((c) => c + 1); // each call gets the latest value: +2 in totalBeispielprojekt: 07_react_grundlagen_examples/ex14_setter_forms
Genauer erklärt: count ist innerhalb eines Renderdurchgangs eine Konstante; beide Zeilen rechnen mit demselben alten Wert, die zweite überschreibt die erste. Die Funktionsform bekommt dagegen von React jeweils den aktuellen Stand. Dasselbe Problem entsteht in setTimeout-Callbacks und nach await: Dort ist der eingefangene Wert oft schon veraltet. Deshalb gilt: Hängt der neue Wert vom alten ab, immer die Funktionsform.
Häufig ist der Zustand keine einzelne Zahl, sondern eine Liste. Genau hier greift Regel 2:
function TagList() { const [tags, setTags] = useState<string[]>(["draft", "urgent"]);
const addTag = (tag: string) => setTags([...tags, tag]); // append const removeTag = (tag: string) => setTags(tags.filter((t) => t !== tag)); // remove const clearTags = () => setTags([]); // replace
return ( <> <ul> {tags.map((tag) => ( <li key={tag}> {tag} <button onClick={() => removeTag(tag)}>x</button> </li> ))} </ul> <button onClick={() => addTag(`tag-${tags.length + 1}`)}>add</button> <button onClick={clearTags}>clear</button> </> );}Beispielprojekt: 07_react_grundlagen_examples/ex15_tag_list
[...tags, tag], tags.filter(...) und [] haben eines gemeinsam: Sie erzeugen ein neues Array, statt das alte zu verändern. Nur so erkennt React die Änderung und rendert die Liste neu.
flowchart LR
accTitle: Verändern gegen Ersetzen eines Arrays im State
old["tags<br/>Array A"] -- "tags.push(x)" --> same["immer noch Array A<br/>React: nichts geändert"]
old -- "[...tags, x]" --> neu["neues Array B<br/>React: neu rendern"]
Abb. 7.17: React vergleicht, ob ein anderes Array im State steht, nicht, ob sich sein Inhalt geändert hat (eigene Darstellung).
Auflösung
Mit count + 1 zeigt er 6: Alle drei Zeilen rechnen mit der Konstante count = 5 dieses Renderdurchgangs und setzen dreimal 6. Mit der Funktionsform zeigt er 8: Jeder Aufruf bekommt von React den Stand nach dem vorherigen, also 6, 7, 8.
State ist privat
Abschnitt betitelt „State ist privat“Zustand gehört zu einer Instanz einer Komponente auf dem Bildschirm. Wer dieselbe Komponente zweimal rendert, bekommt zwei voneinander völlig unabhängige Zustände:
<PresetPicker /> {/* has its own index */}<PresetPicker /> {/* has its own index, too */}Ein Klick in der einen Auswahl lässt die andere unberührt. Das ist der Grund, warum Komponenten wiederverwendbar sind, ohne sich gegenseitig zu stören, und zugleich der Grund, warum gemeinsam genutzter Zustand nach oben wandern muss (Einheit 7.5).
Wo begegnet dir das? Auf einer Seite mit mehreren eingebetteten Videoplayern hat jeder Player seine eigene Abspielposition und Lautstärke. Pausierst du den einen, läuft der andere weiter: ein Baustein, mehrere unabhängige Zustände.
Die Regeln für Hooks
Abschnitt betitelt „Die Regeln für Hooks“useState ist der erste Hook. Für alle Hooks gelten zwei Regeln:
- Nur auf der obersten Ebene aufrufen, nie in
if, in Schleifen oder in verschachtelten Funktionen. React unterscheidet die Hooks einer Komponente über ihre Aufrufreihenfolge; wird ein Aufruf einmal übersprungen, verrutscht die Zuordnung, und der zweite Hook bekommt den Wert des ersten. - Nur in Komponenten oder in eigenen Hooks aufrufen, nicht in gewöhnlichen Funktionen.
Die Vite-Vorlage prüft beide Regeln mit dem Linter: npm run lint startet oxlint, und die Regel react/rules-of-hooks meldet einen Hook in einer Bedingung, bevor die Seite überhaupt läuft.
flowchart LR
accTitle: Hooks werden über ihre Reihenfolge zugeordnet
subgraph r1["1. Rendern"]
direction TB
h1["Hook 1: filename"] --> h2["Hook 2: preset"]
end
subgraph r2["2. Rendern, Hook 1 im if übersprungen"]
direction TB
h2b["erster Aufruf: preset<br/>bekommt Wert von filename"]
end
r1 --> r2
Abb. 7.18: Fällt ein Hook-Aufruf weg, bekommt der nächste den falschen Platz im Notizbuch (eigene Darstellung).
Praktisch heißt das: Hooks stehen ganz oben in der Komponente, so wie import-Zeilen ganz oben in der Datei stehen. Brauchst du einen Zustand nur unter einer Bedingung, kommt trotzdem der Hook nach oben und die Bedingung ins JSX.
Learn useState In 15 Minutes von Web Dev Simplified (Englisch):
Der Render-Kreislauf
Abschnitt betitelt „Der Render-Kreislauf“Alles bisher Gezeigte läuft in einem einzigen, immer gleichen Kreislauf ab. Wer ihn einmal verstanden hat, kann die meisten React-Fragen selbst beantworten:
flowchart LR
accTitle: Der Render-Kreislauf vom Ereignis zur Anzeige
event["Ereignis<br/>(Klick, Eingabe)"] --> handler["Handler ruft Setter"]
handler --> schedule["React plant<br/>neues Rendern"]
schedule --> render["Komponente läuft<br/>erneut durch"]
render --> jsx["neues JSX"]
jsx --> diff["Vergleich mit<br/>dem alten Stand"]
diff --> dom["minimale Änderung<br/>am echten DOM"]
Abb. 7.19: Ereignis, Setter, Rendern, Vergleich, DOM-Änderung, und wieder von vorn (eigene Darstellung).
Drei Folgerungen daraus beantworten erfahrungsgemäß viele Fragen:
- “Rendern” heißt: Die Funktion läuft erneut von oben durch. Jede Berechnung darin wird neu ausgeführt, jede im Rumpf definierte Funktion neu erzeugt. Nur der State überlebt, weil React ihn außerhalb aufbewahrt.
- Der Setter wirkt nicht sofort. Direkt nach
setCount(5)steht incountimmer noch der alte Wert; der neue gilt erst im nächsten Renderdurchgang. Einconsole.log(count)unmittelbar nach dem Setter hilft beim Debuggen deshalb nicht weiter. - Was sich aus State berechnen lässt, ist kein State. Die Anzeige “3 von 8”, die Anzahl der fehlerhaften Jobs, die Warnfarbe ab 260 Zeichen: All das wird beim Rendern ausgerechnet. Ein zusätzlicher State dafür wäre eine zweite Wahrheit, die irgendwann von der ersten abweicht.
Auflösung
State: die Liste der Aufträge, der Suchtext und der aufgeklappte Auftrag; sie entstehen durch Daten oder Benutzeraktionen und lassen sich aus nichts anderem berechnen. Berechnet: die gefilterte Liste (Liste plus Suchtext) und die Zahl der fehlgeschlagenen Aufträge (aus der Liste).
Stolperfallen
Abschnitt betitelt „Stolperfallen“- State direkt verändert:
jobs.push(...)oderjob.state = "done"ändert die Daten, aber nicht die Anzeige. React braucht ein neues Array oder Objekt. - Abgeleitete Werte im State: ein zusätzlicher
failedCount-State neben der Jobliste. Zwei Wahrheiten, die auseinanderlaufen, sobald eine Aktualisierung vergessen wird. - Den Setter-Wert sofort lesen:
setCount(5); console.log(count);zeigt den alten Wert. count + 1statt Funktionsform: Mehrere Setter-Aufrufe in einem Handler rechnen alle mit demselben alten Wert.- Hook in einer Bedingung:
if (x) { const [a, setA] = useState(0); }bricht die Aufrufreihenfolge.npm run lintmeldet das vorab (Regelreact/rules-of-hooks), React selbst erst zur Laufzeit.
Zusatzmaterialien
Abschnitt betitelt „Zusatzmaterialien“- react.dev: State: a component’s memory - die Einführung in
useStatemit interaktiven Beispielen. - react.dev: State as a snapshot - warum der State-Wert innerhalb eines Renderdurchgangs konstant bleibt.
- react.dev: Updating arrays in state - Hinzufügen, Entfernen und Ändern ohne
pushundsplice. - react.dev: Rules of Hooks - die Hook-Regeln mit Beispielen für erlaubte und verbotene Aufrufe.
Zum Anhören
Abschnitt betitelt „Zum Anhören“Du kannst dir diese Einheit kostenlos mit der Vorlesefunktion deines Browsers oder Betriebssystems vorlesen lassen (etwa „Laut vorlesen” in Edge), ohne Konto und ohne Upload; die Codebeispiele liest du dabei am Bildschirm mit. Freiwillig kannst du mit Google NotebookLM (Google-Konto nötig) aus diesem öffentlichen Text eine gesprochene Zusammenfassung erzeugen, auch auf Deutsch.
Lernergebnisse
Abschnitt betitelt „Lernergebnisse“Du kannst jetzt:
- erklären, warum eine lokale Variable sich nicht als Zustand eignet, und Zustand mit
useStateverwalten. - State nur über den Setter und Arrays und Objekte immutabel aktualisieren, mit der Funktionsform, wenn der neue Wert vom alten abhängt.
- die Regeln für Hooks nennen und begründen.
- den Render-Kreislauf beschreiben und entscheiden, ob ein Wert State ist oder beim Rendern berechnet wird.
Passende Übungen
Abschnitt betitelt „Passende Übungen“- Aufgabe 07-4a - State-Training, Stufe Wesentlich_überwiegend
Quellen
Abschnitt betitelt „Quellen“useState, Startwert, Setter → react.dev: State: a component’s memory, useState- State als Momentaufnahme, Funktionsform des Setters → react.dev: State as a snapshot, Queueing a series of state updates
- Arrays im State → react.dev: Updating arrays in state
- Regeln für Hooks → react.dev: Rules of Hooks
oxlintmitreact/rules-of-hooksin der Vite-Vorlage → eigener Test mitnpm create vite@latest(Vite 8.3.4, Oktober 2026)- Ergebnis 6 und 8 bei drei Setter-Aufrufen → eigener Test im Beispielprojekt
ex14_setter_forms(Oktober 2026) - Video Learn useState In 15 Minutes → Web Dev Simplified auf YouTube: O6P86uwfdR0
- Abb. 7.14 (Notizbuch), Abb. 7.15 (lokale Variable), Abb. 7.16 (
useStateüber zwei Durchgänge), Abb. 7.17 (Verändern gegen Ersetzen), Abb. 7.18 (Hook-Reihenfolge), Abb. 7.19 (Render-Kreislauf) → eigene Darstellung, alle als Mermaid-Diagramm
Autoren
Abschnitt betitelt „Autoren“- Oliver Maurhart (MAO)
- Michael Gabriel (GAM)
CC-BY-SA