useState
Etwas merken, das sich ändern darf
useState gibt deiner Komponente ein Gedächtnis: einen Wert, der die Durchläufe überlebt, plus einen Knopf, mit dem du ihn änderst und React zum Neuzeichnen bringst.
Das ist der Hook, den du am häufigsten benutzen wirst. Alles, was sich auf dem Bildschirm ändern soll, steckt in einem useState.
const [wert, setWert] = useState(startwert);
// ▲ ▲ ▲
// | | zählt nur beim allerersten Durchlauf
// | ändert den Wert und lässt React neu zeichnen
// der Wert dieses Durchlaufs Eine normale Variable in einer Komponente ist ein Zettel, der nach jedem Durchlauf weggeworfen wird. useState ist das Notizbuch, das React für dich aufbewahrt – und sobald du hineinschreibst, malt React das Bild neu.
Wofür du es brauchst
- +
Text in einem Eingabefeld
- +
Auf/Zu, an/aus, ausgewählter Tab
- +
Zähler, Auswahl, geladene Daten
- +
Kurz: alles, was sich ändert und dabei sichtbar ist
- −
Werte, die du aus anderem State ausrechnen kannst – rechne sie einfach aus
- −
Dinge, die niemand sieht (z. B. eine Timer-Nummer) → dafür gibt es RefEin kleiner Merkzettel, den React zwischen den Renders aufhebt – ohne dass eine Änderung daran etwas neu zeichnet.→
- −
Eine Prop einfach nur kopieren – die Kopie veraltet früher oder später
Zwei Arten zu setzen
// 1) Direkt – wenn der neue Wert unabhängig vom alten ist
setName("Ada");
setOffen(false);
// 2) Als Funktion – wenn der neue Wert auf dem alten aufbaut
setCount((c) => c + 1);
setItems((bisher) => [...bisher, neu]); Taucht der alte Wert rechts auf (count + 1, [...items, x]), nimm die Funktionsform. Sie bekommt garantiert den aktuellsten Stand – auch wenn du mehrfach hintereinander setzt oder der Aufruf verzögert passiert.
Nie das Alte ändern – immer ersetzen
user.name = "Ada";
setUser(user);
todos.push(neu);
setTodos(todos); setUser({ ...user, name: "Ada" });
setTodos((bisher) => [...bisher, neu]); React schaut nur, ob es dasselbe Objekt ist wie vorher. Änderst du es innen, ist es immer noch dasselbe – und React zeichnet nicht neu. Merksatz: kopieren, ändern, setzen.
// hinzufügen
setItems((p) => [...p, neu]);
// entfernen
setItems((p) => p.filter((i) => i.id !== id));
// eins davon ändern
setItems((p) => p.map((i) => (i.id === id ? { ...i, done: true } : i)));
// ein Feld in einem Objekt ändern
setForm((f) => ({ ...f, email: "a@b.de" })); setForm({ email: v }) ersetzt das ganze Objekt – der Name ist danach weg. Anders als früher in Klassen führt React hier nichts automatisch zusammen. Deshalb immer erst ...f hinschreiben.
Nicht speichern, was sich ausrechnen lässt
const [items, setItems] = useState([]);
const [anzahl, setAnzahl] = useState(0);
function add(x) {
setItems([...items, x]);
setAnzahl(anzahl + 1); // wehe, das vergisst jemand beim Löschen
} const [items, setItems] = useState([]);
const anzahl = items.length; // immer korrekt
function add(x) {
setItems((p) => [...p, x]);
} Frag dich bei jedem neuen State: Kann ich das aus etwas anderem ausrechnen? Wenn ja, ist es kein State.
▸ Genauer erklärt: teurer Startwert, Funktionen im State, Batching optional
Startwert nur einmal berechnen
Der Startwert wird ab dem zweiten Durchlauf ignoriert – die Berechnung läuft aber trotzdem jedes Mal, wenn du sie direkt hinschreibst. Übergibst du stattdessen eine Funktion, ruft React sie nur beim ersten Mal auf.
// ❌ liest bei JEDEM Durchlauf aus dem Speicher
const [form, setForm] = useState(JSON.parse(localStorage.getItem("f") ?? "{}"));
// ✅ liest nur beim ersten Mal
const [form, setForm] = useState(() => JSON.parse(localStorage.getItem("f") ?? "{}")); Wenn du wirklich eine Funktion speichern willst
setState(meineFunktion) versteht React als „hier ist die Funktionsform“ und ruft sie auf. Willst du die Funktion selbst ablegen, brauchst du eine Ebene mehr: setState(() => meineFunktion).
Warum mehrere setState nur einmal zeichnen
React sammelt alle Änderungen eines Ereignisses und zeichnet danach einmal neu – das nennt sich BatchingReact sammelt mehrere Änderungen und zeichnet danach einmal neu, statt nach jeder einzelnen.→. Seit React 18 gilt das auch in setTimeout und in Promises. Direkt nach setCount(5) steht in count deshalb noch der alte Wert.
function handleClick() {
setA(1);
setB(2);
setC(3);
// React zeichnet EINMAL neu, nicht dreimal
}
// Brauchst du den neuen Wert sofort, rechne ihn dir separat aus:
function handleClick2() {
const naechster = count + 1;
setCount(naechster);
sendeAnServer(naechster); // nicht count benutzen – der ist noch alt
} Sitzt das schon?
5 Fragen zu dieser Lektion. Falsche Antworten landen in deiner Statistik.
Jetzt selbst schreiben
Baue den Zähler fertig. Der Knopf +2 ist der interessante: Er soll zweimal setCount aufrufen und wirklich um 2 erhöhen – der naive Weg schafft nur 1.
- □
+1erhöht die Anzeige um 1 - □
+2 (zweimal +1)erhöht um 2 – mit zwei setCount-Aufrufen - □
Zurücksetzensetzt auf 0
import { useState } from "react"; export default function App() { const [count, setCount] = useState(0); function plusEins() { // TODO } function plusZwei() { // TODO: zweimal setCount aufrufen – Ergebnis muss +2 sein } function reset() { // TODO } return ( <div style={{ fontFamily: "system-ui", padding: 16 }}> <p data-testid="count" style={{ fontSize: 32 }}>{count}</p> <button onClick={plusEins}>+1</button>{" "} <button onClick={plusZwei}>+2 (zweimal +1)</button>{" "} <button onClick={reset}>Zurücksetzen</button> </div> ); }
Vervollständige die drei Funktionen. Wichtig: das vorhandene Array nie verändern, sondern jeweils ein neues bauen.
- □
Hinzufügenhängt den Text an und leert das Eingabefeld - □Ein Klick auf einen Eintrag streicht ihn durch
- □
×entfernt den Eintrag
import { useState } from "react"; type Todo = { id: number; text: string; done: boolean }; export default function App() { const [todos, setTodos] = useState<Todo[]>([]); const [text, setText] = useState(""); function add() { if (!text.trim()) return; // TODO: neues Todo anhängen und das Eingabefeld leeren } function toggle(id: number) { // TODO: done umschalten } function remove(id: number) { // TODO: Eintrag entfernen } return ( <div style={{ fontFamily: "system-ui", padding: 16 }}> <input aria-label="todo" value={text} onChange={(e) => setText(e.target.value)} placeholder="Was ist zu tun?" />{" "} <button onClick={add}>Hinzufügen</button> <ul> {todos.map((t) => ( <li key={t.id}> <span onClick={() => toggle(t.id)} style={{ textDecoration: t.done ? "line-through" : "none", cursor: "pointer" }} > {t.text} </span>{" "} <button aria-label={"loeschen-" + t.text} onClick={() => remove(t.id)}>×</button> </li> ))} </ul> </div> ); }