Zum Inhalt springen

Aufgabe 12 - State-Training

Zu Zen-Modus wechseln

Der Umgang mit State ist die zentrale Denkumstellung bei React. An vier kleinen Komponenten trainieren Sie, Zustand richtig zu modellieren (was ist State, was ist ableitbar?), immutabel zu aktualisieren und an die richtige Stelle der Hierarchie zu heben (siehe Kapitel React-Grundlagen).

  • Kapitel React-Grundlagen, Abschnitte State und Datenfluss.
  • Das Next.js-Projekt aus Aufgabe 11 (oder ein frisches über npx create-next-app@latest, siehe dort); ein Vorgriff auf Kapitel 8. Next.js I - Struktur und Routing. Ihre Übungskomponenten entstehen darin unter app/.
  • Sie entscheiden begründet, welche Werte State sind und welche berechnet werden.
  • Sie aktualisieren State immutabel, insbesondere Arrays und Objekte.
  • Sie heben gemeinsamen State in den nächstgemeinsamen Vorfahren.
  • Reproduktion: das useState-Muster an einfachen Komponenten anwenden (Teil A).
  • Reorganisation und Transfer: Zustandsmodelle analysieren und Aktualisierungsregeln übertragen (Teile B und C).
  • Reflexion, Problemlösung und Urteilsbildung: eine Hierarchie-Entscheidung treffen und verteidigen (Teil D).

Die Übung ist auf etwa zwei Stunden ausgelegt. Teil D ist der Expertenteil.

So richten Sie die Übungsseite ein (einmal am Anfang, dann geht es los):

  1. Legen Sie im Projekt den Ordner app/uebung/ an und darin eine Datei page.tsx. Diese Seite erreichen Sie im Browser unter http://localhost:3000/uebung, sobald der Entwicklungsserver läuft (npm run dev).
  2. Jede der vier Komponenten schreiben Sie in eine eigene Datei, etwa app/uebung/Thumbs.tsx. Wichtig: Jede Komponente, die useState verwendet, braucht in Next.js ganz oben in der Datei die Zeile "use client"; (sonst läuft sie als Server-Komponente und useState bricht ab). Diese Fehlermeldung werden Sie mindestens einmal sehen; sie ist der häufigste Stolperstein in dieser Übung.
  3. In page.tsx importieren Sie die Komponenten und rendern sie untereinander. So wächst die Seite Stück für Stück:
import { Thumbs } from "./Thumbs";
import { CharacterCounter } from "./CharacterCounter";
export default function UebungPage() {
return (
<main>
<Thumbs />
<CharacterCounter />
{/* the other components get added here as you build them */}
</main>
);
}

Arbeiten Sie eine Komponente nach der anderen ab: Datei anlegen, in page.tsx einhängen, im Browser ausprobieren, dann die nächste.

  1. Thumbs - ein Daumen-hoch/-runter-Zähler. Konkret: zwei Buttons und dazwischen eine Zahl, die die Differenz zeigt. Schritte:
    • Legen Sie mit useState fest, was Sie speichern. Treffen Sie jetzt die Entscheidung: ein einziger State (die Differenz) oder zwei getrennte States (up und down)? Schreiben Sie Ihre Wahl und die Begründung als Kommentar über die Komponente (Tipp: Was müssten Sie ändern, wenn später “wie viele Stimmen insgesamt?” dazukäme? Das ist ein Argument für zwei States).
    • Der Klick auf “hoch” erhöht, der Klick auf “runter” verringert. Verwenden Sie die Setter-Funktionsform: setUp((current) => current + 1).
    • Zeigen Sie die Differenz an. Wenn Sie zwei States gewählt haben, wird sie berechnet (up - down), nicht gespeichert.
  2. CharacterCounter - ein Textfeld mit Live-Zeichenzähler wie bei einer Kurznachricht. Schritte:
    • Ein <textarea> als controlled component: Sein value kommt aus dem State, onChange schreibt jede Eingabe zurück in den State (siehe Kapitel React-Grundlagen, Abschnitt Formulare).
    • Darunter live “42 / 280 Zeichen”. Die aktuelle Länge ist text.length, also abgeleitet, kein eigener State.
    • Ab 260 Zeichen bekommt die Anzeige eine Warnfarbe. Leiten Sie die CSS-Klasse aus dem State ab: className={text.length > 260 ? "warn" : ""}. Kein document.querySelector, kein manuelles Umfärben.

ShoppingList - eine Einkaufsliste: ein Eingabefeld plus “Hinzufügen”-Button hängt einen Eintrag an, ein Klick auf einen Eintrag entfernt ihn wieder. Schritte:

  1. Zwei States: der aktuelle Text des Eingabefelds (String) und die Liste der Einträge (string[]). Fügen Sie immutabel hinzu und entfernen Sie immutabel: setItems([...items, newItem]) zum Anhängen, setItems(items.filter((item) => item !== clicked)) zum Entfernen.
  2. Pflicht-Experiment: Bauen Sie das Hinzufügen absichtlich einmal falsch mit items.push(newItem) (ohne Setter). Beobachten Sie, dass die Anzeige sich nicht aktualisiert, und beschreiben Sie im Kommentar, warum React die Änderung nicht bemerkt. Danach zurück zur korrekten Version.
  3. Leere Eingaben sollen nicht in die Liste: Deaktivieren Sie den Button, solange das Eingabefeld leer ist (disabled={text.trim() === ""}) - auch das ist ein aus dem State abgeleiteter Wert.
  1. DicePair - zwei Würfel und ein “Würfeln”-Button. Schritte:
    • Zwei States für die beiden Augenzahlen (1 bis 6). Der Button setzt beide neu (Math.floor(Math.random() * 6) + 1).
    • Bei einem Pasch (beide gleich) erscheint eine Meldung “Pasch!”. Diese Meldung ist kein eigener State, sondern wird beim Rendern aus den beiden Würfelwerten berechnet (dieA === dieB).
    • Begründen Sie im Kommentar, welcher Fehler bei einem zusätzlichen isDouble-State entstehen könnte (Stichwort: zwei Wahrheiten, die auseinanderlaufen, wenn man einmal vergisst, beide zu aktualisieren).
  2. Erstellen Sie für alle vier Komponenten eine State-Tabelle (Variable, echter State oder abgeleitet, Begründung in Stichworten). Prüfkriterium für “echter State”: Er entsteht nur durch eine Benutzeraktion und lässt sich aus nichts anderem berechnen.

Ausgangslage: Ihre vier Komponenten funktionieren, kennen einander aber nicht. Jetzt kommt eine Anforderung, die sie verbindet.

  1. Über allen vier Komponenten soll eine Leiste “Interaktionen gesamt: n” jede Aktion in jeder Komponente mitzählen. Entwerfen Sie zuerst auf Papier: Wo muss dieser Zähler leben, und warum kann er in keiner der vier Komponenten selbst wohnen? (Antwort in Stichworten notieren.)
  2. Implementieren Sie das Muster State heben: Der Zähler-State wandert in die Elternkomponente (UebungPage oder eine neue Leisten-Komponente darüber). Jede der vier Komponenten bekommt eine Callback-Prop onInteraction: () => void und ruft sie bei jeder Aktion auf; kein Kind kennt den Zähler selbst. Das ist “Props runter, Events rauf” in Reinform.
  3. Härtetest: Die Leiste soll zusätzlich anzeigen, welche Komponente zuletzt benutzt wurde. Erweitern Sie die Signatur zu onInteraction(source: string) (jedes Kind meldet seinen Namen mit) und begründen Sie in zwei Sätzen, warum die Alternative - jedes Kind schreibt direkt in eine globale Variable - den Einweg-Datenfluss von React zerstören würde.
  4. Beurteilen Sie in zwei Sätzen: Nach welcher Regel entscheiden Sie ab jetzt, auf welcher Ebene der Hierarchie ein State wohnen muss?
  1. Woran erkennen Sie, dass ein Wert kein State sein sollte?
  2. Warum bewirkt items.push(...) keine Aktualisierung der Anzeige?
  3. Was ist der Vorteil der Setter-Funktionsform setCount((c) => c + 1)?
  4. Was bedeutet “Props runter, Events rauf” konkret in Teil D?
  5. Welche zwei Wahrheiten können bei einem redundanten isDouble-State auseinanderlaufen?

Git-Repository mit der Übungsseite; die State-Tabelle aus Teil C in der README.