Reacts useState()-Hook ist ein zentrales Werkzeug zur Verwaltung von Zuständen in Funktionskomponenten. In diesem Beitrag werfen wir einen präzisen Blick darauf, wie useState() funktioniert, welche Prinzipien dahinterstehen und wie sich Zustandsänderungen auf das Verhalten einer Komponente auswirken.

Einführung in die Funktionsweise

Durch den Aufruf von useState() innerhalb eines Components werden bestimmte Daten registriert. Es besteht eine Ähnlichkeit zu Definition von Variablen oder Konstanten in reinem JavaScript. Allerdings gibt es eine Besonderheit. React überwacht die registrierten Elemente intern. Bei einer Änderung von diesen überwachten Daten wird React das Component neu evaluieren.

Das erfolgt durch die Prüfung seitens React auf Änderung der verwendeten Daten im Component. Das Wichtigste ist, dass eine Überprüfung stattfindet, ob die UI aktualisiert werden soll, weil sich die Daten geändert haben, weil, beispielsweise, diese Daten innerhalb von JSX eingesetzt werden. Ist ein Update erforderlich, wird React das echte DOM an der Stelle aktualisieren, an welcher diese Daten verwendet werden. Wenn kein Update notwendig ist, endet die Re-Evaluierung ohne Aktualisierung von DOM.

Der gesamte Prozess beginnt mit dem Aufruf von useState() innerhalb des Components. Dabei wird ein Zustandswert erzeugt (gespeichert und verwaltet intern durch React) und dem entsprechenden Component zugeordnet.

Initialer Wert wird durch die Angabe des Parameters an useState() gesetzt. In unserem Beispiel ist useState(0). Spricht, der initiale Wert ist gleich 0.

TypeScript CounterClassBased.jsx
import { useState } from 'react';

const CounterUseStateBased = () => {
    const [currentCounter, setCounter] = useState(0);

    return (
        <div>
            <p>Anzahl Klicks: {currentCounter}</p>
            <button onClick={() => setCounter(currentCounter + 1)}>
                Klick
            </button>
        </div>
    );
};

export default CounterUseStateBased;

Folgende Zeile ist die Stelle, an der ein Zustand bzw. zu überwachende Dateneinheiten (in diesem Fall currentCounter) registriert werden.

JSX
const [currentCounter, setCounter] = useState(0);

Wie man allerdings am Konstrukt sehen kann, nimmt die useState() Funktion nicht nur einen initialen Wert an, sondern gibt auch bestimmte Sachen zurück.

Die Funktion useState() gibt immer zwei Elemente/Werte zurück.

  • Erstes Element: Aktueller Wert
  • Zweites Element: Funktion, die zum Setzen eines neuen Wertes aufgerufen wird Im oberen Beispiel wird Destruktion eingesetzt. Man könnte das obere Beispiel auch folgendermaßen umschreiben.
JSX Alternative
const counterState = useState(0);
const currentCounter = counterState[0];
const setCounter = counterState[1];

Blick unter die Haube von React

Zugriff auf den Wert

React verwaltet die Zustände in einem internen Speicher, der für uns Entwickler nicht zugänglich ist. Weil wir aber oft auf die Werte in der Zustandsverwaltung zugreifen müssen, stellt uns React einen Weg zur Verfügung, um sie auszulesen. Das erfolgt durch den ersten Wert des Rückgabewertes der useState() Funktion. Das erste Element beinhaltet immer den aktuellen Wert. Dieser Wert kann entsprechend für die Ausgabe oder an anderen Stellen im Component verwendet werden.

Setzen eines Werts

Ebenfalls müssen Werte in der Zustandsverwaltung (State Management) regelmäßig aktualisiert werden. Weil aber die Verwaltung nicht auf einem direkten Wege erfolgt, sondern durch React übernommen wird, stellt uns React eine Funktion bereit, mit der wir den Wert aktualisieren können. Durch die Verwendung dieser Funktion wird React über eine Änderung informiert und kann nachgelagerte Aktualisierung(en) durchführen. Das ist das zweite Element im Rückgabewert der useState() Funktion.

Im oberen Beispiel haben wir die Funktion setCounter genannt. Diese rufen wir auf und übergeben einen Wert als Parameter, welcher als neuer Wert gesetzt werden soll.

Wichtiger Hinweis

React aktualisiert nicht nur das aktuelle Component beim Aufruf der Funktions für die Zustandsänderung, sondern auch alle Child-Components, für die diese Änderung eine Auswirkung hat.

Konstanten bei Destruktion

Wenn der Rückgabewert der useState() Funktion bereits bei der Zuweisung in einzelne Elemente zerlegt wird, erfolgt in den meisten Fällen eine Zuweisung mit const. An dieser Stelle könnte die Frage entstehen, wie es möglich ist, dass die Werte dennoch aktualisiert werden können.

Der Grund hierfür ist die Aktualisierung bzw. eine Neu-Ausführung des Components. Im Grunde erfolgt bei einem Update eine neue Zuweisung. Auch die Funktion useState() wird in Fall eines Updates neu ausgeführt.

Hinweis

Weil bei einer Änderung das Component neu aufgebaut und die Funktion useState() neu aufgerufen wird, wird auch ein neuer Wert in das erste Element des Rückgabewerts hineingeschrieben.

Namenskonventionen

Typischerweise wird tatsächlich die destruktive Version bei Verwendung von useState() eingesetzt.

JSX Konvention
const [myVar, setMyVar] = useState('');

Bekanntlich ist es technisch problemlos möglich die Namen bei der Destruktion frei wählen zu können. Allerdings gibt es in React eine Namenskonvention, die uns mitteilt, wie am besten diese Variablennamen zusammengesetzt werden sollen.

Das erste Element

JSX Erstes Element
const [currentValue, updateFunction] = userState();

Das erste Element (der aktuelle Wert) sollte so gewählt werden, dass am besten den Zweck beschreibt und man anhand dem Namen ableiten kann, welchen Zweck diese Variable hat bzw. welche Daten sie beinhaltet.

Folgende Beispiele sind gut.

  • enteredPrice
  • userEmail
  • productCounter
  • ...

Generische Namen oder Namen wir setValue sollten vermieden werden.

Das zweite Element

JSX Zweites Element
const [currentValue, updateFunction] = userState();

Das zweite Elemente, der Name der Funktion, die zum Setzen eines neuen Wertes verwendet wird, sollte wie folgt gewählt werden. Es soll klar sein, dass es sich um eine Funktion handelt und was sie tut. Im Allgemeinen besagt die Namenskonvention, dass das Schema so aussieht: setXYZ, wobei XYZ der Name ist, welchen man für das erste Element vergeben hat.

Folglich wird aus enteredPrice der Name der Funktion setEnteredPrice. Oder aus userEmail wird setUserEmail.

Funktionale Setter-Form

Der Setter akzeptiert nicht nur einen direkten Wert, sondern auch eine Funktion, die den vorherigen Wert bekommt und den neuen zurückgibt:

TypeScript FunktionalerSetter.jsx
const [count, setCount] = useState(0);

// Direkter Wert
setCount(count + 1);

// Funktionale Form — sicherer bei mehreren Aufrufen
setCount((prev) => prev + 1);

// Klassischer Fehler: drei Mal hochzählen
const handleClick = () => {
    setCount(count + 1);  // count ist hier noch 0
    setCount(count + 1);  // immer noch 0
    setCount(count + 1);  // → Endergebnis: 1, nicht 3
};

// Korrekt mit funktionalem Setter
const handleClickRichtig = () => {
    setCount((c) => c + 1);
    setCount((c) => c + 1);
    setCount((c) => c + 1);   // → 3
};

Faustregel: wenn der neue Wert vom alten abhängt, immer die funktionale Form nutzen. Sie ist robust gegen Race-Conditions und multiple Setter-Aufrufe im selben Event.

Batching — mehrere Setter, ein Render

Seit React 18 werden mehrere Setter-Aufrufe in einem Event automatisch gebatcht: React wartet, bis alle Setter durchgelaufen sind, und führt dann genau einen Re-Render aus.

TypeScript Batching.jsx
const handleClick = () => {
    setName('Anna');
    setAge(30);
    setActive(true);
    // → React rendert die Komponente nur EINMAL neu
};

Vor React 18 galt Batching nur innerhalb von Event-Handlern. Mit React 18 gilt es überall — auch in Promises, setTimeout, native Event-Handler. Wer das alte Verhalten will: flushSync aus react-dom.

Object.is — wann React keinen Re-Render macht

React vergleicht den neuen State per Object.is mit dem alten. Sind beide identisch, rendert React nicht neu.

TypeScript ObjectIs.jsx
const [user, setUser] = useState({ name: 'Anna' });

// Kein Re-Render — exakt dieselbe Referenz
setUser(user);

// KEIN Re-Render obwohl Wert „gleich" — neue Referenz aber Object.is(neu, alt) === false
// → DOCH Re-Render, weil neue Object-Referenz
setUser({ name: 'Anna' });

// Kein Re-Render — Primitive gleich
const [count, setCount] = useState(5);
setCount(5);

Bei Objekten und Arrays muss eine neue Referenz übergeben werden — Mutation des alten State löst keinen Re-Render aus (siehe Immutability).

Interessantes

useState gibt IMMER ein Tupel zurück — destructuring ist Konvention.

useState(0) liefert [wert, setter]. Destructuring ist nur Syntax — man könnte auch const state = useState(0); state[0]; state[1]; schreiben. Lesbarer ist die destrukturierte Form.

Naming-Konvention: setXYZ für den Setter.

const [name, setName] = useState('') ist die Standard-Form. Andere Namen (updateName, changeName) funktionieren technisch, sind aber stilistisch unüblich. Linter-Plugins erkennen das Pattern für besseres Debugging.

Setter ist STABIL — bleibt zwischen Renders identische Referenz.

setName hat dieselbe Referenz in jedem Render. Daher kann man setName in useEffect-Dependencies weglassen, ohne dass Linter warnt. Anders bei selbst geschriebenen Update-Funktionen.

Funktionaler Setter rettet bei abhängigen Updates.

setCount((c) => c + 1) liest den AKTUELLEN State, nicht den im Closure eingefrorenen. Wenn mehrere Setter im selben Event den Wert basierend auf dem vorherigen erhöhen sollen: IMMER funktionale Form.

Object.is als Vergleich — neue Referenz nötig für Objects.

React rendert nicht, wenn Object.is(neu, alt) true ist. Bei Primitives (Number, String, Boolean) reicht der Wert. Bei Objects/Arrays MUSS eine neue Referenz übergeben werden — Mutation des alten reicht NICHT.

Mehrere setState in einem Event werden gebatcht.

React 18 batcht überall — Promises, setTimeout, native Listener. Das Ergebnis: ein Re-Render pro Event, nicht pro Setter. Wer altes Verhalten braucht (selten): flushSync aus react-dom.

setter aus useState kann auch unverändert aufgerufen werden — kein Re-Render.

setCount(count) mit demselben Wert ist sicher — kein Re-Render, kein State-Change. Nützlich für conditional Updates: setCount(c => cond ? c + 1 : c).

const bei destructuring ist OK — bei jedem Render NEU zugewiesen.

Anfänger-Frage: „Wie kann const ein veränderlicher Wert sein?" Antwort: bei jedem Render läuft die Komponenten-Funktion neu, useState liest den AKTUELLEN Wert aus dem React-internen Speicher. Daher entsteht eine NEUE const-Bindung mit dem neuen Wert.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu State

Zur Übersicht