State bezeichnet den internen Zustand einer Komponente, der sich über die Lebensdauer hinweg verändern kann. Er ermöglicht das Reagieren auf Benutzerinteraktionen, Eingaben und andere Ereignisse. Mit Hooks wie useState lassen sich Werte speichern, aktualisieren und gezielt ins Rendering einbinden, sodass die Benutzeroberfläche automatisch auf Änderungen reagiert.
Was ist State?
In React bezeichnet State (Zustand) ein zentrales Konzept, das es Komponenten erlaubt, Daten intern zu speichern und dynamisch auf Änderungen zu reagieren. Anders als bei Props, die von außen an eine Komponente übergeben werden, wird State innerhalb einer Komponente verwaltet und kann sich im Laufe der Zeit ändern - z.B. durch Benutzer-Interaktionen oder API-Antworten.
State ist ein Objekt, das Daten enthält, die sich im Laufe des Lebenszyklus einer Komponente ändern können. Wird der State geändert, rendert React die Komponente (und ihre Kind-Komponenten) neu, um die Änderungen in UI zu zeigen.
State in Klassenkomponenten (traditionell)
Bevor wir zu modernen Hooks kommen, werfen wir einen kurzen Blick auf State in Klassenkomponenten, um das Konzept besser zu verstehen.
Dazu erstellen wir ein Component CounterClassBased und werden in diesem Component den klassenbasierten Ansatz einsetzen.
import React, { Component } from 'react';
class CounterClassBased extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Anzahl Klicks: {this.state.count}</p>
<button onClick={this.increment}>Klick</button>
</div>
);
}
}
export default CounterClassBased;Wenn man nun dieses Component irgendwo verwendet, erhält man funktionierendes Stück der Anwendung.
<CounterClassBased />
Hook useState()
Mit der Einführung von React Hooks in React 16.8 wurde das State-Management in Funktionskomponenten revolutioniert. Der wichtigste Hook für State-Management ist useState.
Hooks stellen ein Schlüsselkonzept in React dar. Dies sind spezielle Funktionen, welche nur innerhalb von React Components oder innerhalb von anderen Hooks verwendet werden können. Hooks fügen bestimmte Funktionen und Verhalten zu React Components, in denen sie verwendet werden.
Beispielsweise der Hook useState() ermöglicht es einem Component einen bestimmten Zustand zu setzen und zu verwalten.
Beispiel mit useState()
Wir werden nun das obige Beispiel, welches mit einem klassenbasierten Ansatz geschrieben war, umbauen und nach eine Funktion umschreiben useState() verwenden.
import { useState } from 'react';
const CounterUseStateBased = () => {
const [currentCounter, setCurrentCounter] = useState(0);
return (
<div>
<p>Anzahl Klicks: {currentCounter}</p>
<button onClick={() => setCurrentCounter(currentCounter + 1)}>
Klick
</button>
</div>
);
};
export default CounterUseStateBased;Die Verwendung erfolgt auf klassischem Wege.
<CounterUseStateBased />Das Ergebnis ist identisch mit zu dem oberen Beispiel. Allerdings wird hier der moderne Ansatz verwendet.

State vs. Variable
Eine häufige Anfänger-Frage: warum nicht einfach eine normale Variable nutzen?
// FUNKTIONIERT NICHT als Zähler
const Broken = () => {
let count = 0;
return (
<div>
<p>Klicks: {count}</p>
<button onClick={() => { count++; console.log(count); }}>
Klick
</button>
</div>
);
};Hier wird zwar count in der Konsole hochgezählt, aber die UI ändert sich nie — weil React nichts vom Wechsel weiß. Bei jedem Render entsteht eine neue lokale Variable mit Initialwert 0. Genau diese Probleme löst useState: Wert bleibt zwischen Renders erhalten, und Änderungen triggern automatisch ein Re-Rendering.
Wann brauche ich State?
State ist dann sinnvoll, wenn ein Wert sich über die Zeit ändert UND wenn diese Änderung sichtbar werden soll. Klassische Anwendungsfälle:
- Form-Eingaben: Wert des Inputs, Validierungs-Status
- UI-Modi: Modal offen/zu, Tab aktiv, Theme dark/light
- Geladene Daten: API-Antwort, Loading-State, Fehler
- Lokale Zähler: Like-Count, Step im Wizard, Pagination
- Toggles & Filters: Checkbox-Status, gewählte Kategorien
Nicht in State gehören: Werte, die sich aus anderem State ableiten lassen (z.B. isValid aus email-Wert) — die berechnet man im Render-Body neu. Und Werte, die das DOM ohnehin schon kennt (z.B. der aktuelle Scroll-Wert) — die liest man bei Bedarf direkt aus.
Interessantes
State-Änderung triggert ein Re-Rendering.
Genau das ist der Punkt: setState sagt React „bitte rendere neu". Eine direkte Mutation einer normalen Variable löst kein Rendering aus — die UI bleibt veraltet, auch wenn der Wert intern stimmt.
useState gibt ein Tupel zurück: [wert, setter].
Per Konvention destrukturiert: const [count, setCount] = useState(0). Die Namen sind frei wählbar — der Setter beginnt aber konventionell mit set (z.B. setIsOpen für isOpen).
Klassen-Komponenten sind in neuem Code nicht mehr nötig.
Seit React 16.8 (2019) sind Hooks der Standard. Klassen-Komponenten funktionieren weiter, aber alle neuen Konzepte (Suspense, Concurrent, useTransition) sind hook-only. Bestehende Klassen-Komponenten zu migrieren ist optional, aber lohnenswert.
State ist lokal — pro Komponenten-Instanz eigene Werte.
Zwei <Counter />-Instanzen haben jeweils ihren eigenen count. State teilt sich NICHT automatisch zwischen Instanzen. Für geteilten State: Lifting State Up oder Context.
State-Setter sind ASYNCHRON aus Sicht des aktuellen Renders.
Nach setCount(c + 1) hat count in derselben Funktion noch den ALTEN Wert. Der neue Wert ist erst im NÄCHSTEN Render verfügbar. Wer mehrere Updates verkettet: Funktional-Form setCount(c => c + 1) nutzen.
State NIE direkt mutieren — immer den Setter nutzen.
state.value = 5 oder state.push(x) ist ein Anti-Pattern. React vergleicht per Reference-Identity. Wer State mutiert: neuer State, neuer Wert, neue Referenz. Details im Immutability-Artikel.
Hook-Regeln: nur auf Top-Level der Komponente, nie in if/loops.
useState muss IMMER in derselben Reihenfolge aufgerufen werden. if (cond) useState(...) ist ein React-Fehler. Die Bedingung gehört INNERHALB des Setters oder als Branching im Return.
Lazy Initial: useState(() => teuer()) statt useState(teuer()).
Wenn der Initialwert teuer zu berechnen ist (z.B. localStorage lesen), übergibt man eine FUNKTION an useState. Diese wird nur beim Mount aufgerufen, nicht bei jedem Render. Details im Lazy Initial State-Artikel.