Zum Inhalt springen
aviral gupta

// B1.3 · ca. 30 Min. · Einstieg

State hinzufügen

Nach dieser Lektion reagieren Sie auf Klicks, halten Werte mit useState im State und lassen jedes Tic-Tac-Toe-Feld beim Klick ein X zeigen.

Lektion 3 von 5 in B1 Schnellstart

Danach können Sie

  • Einen Event-Handler an onClick übergeben, ohne ihn aufzurufen
  • State mit useState deklarieren und die Anzeige durch Aufruf des Setters aktualisieren
  • Erklären, warum jede Instanz einer Komponente ihren eigenen State hat, und Hooks nur oben in einer Komponente aufrufen
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus Lektion B1.2: Props. Welchen Text zeigt App?

    function Greeting({ name, wins }) {
      return <p>{name} has {wins + 1} wins</p>;
    }
    
    function App() {
      return <Greeting name="Ada" wins={2} />;
    }
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie voraus, bevor Sie weiterlesen. Sie klicken dreimal auf diesen Button. Was zeigt er dann?

    function Counter() {
      const [count, setCount] = useState(0);
    
      function handleClick() {
        setCount(count + 1);
      }
    
      return <button onClick={handleClick}>Clicked {count} times</button>;
    }
  3. Üben · Aufgabe 3 von 7

    Ergänzen Sie den Attributwert so, dass ein Klick auf den Button X zeigt.

    function Square() {
      const [value, setValue] = useState(null);
    
      function handleClick() {
        setValue('X');
      }
    
      return <button className="square" onClick=____>{value}</button>;
    }
    onClick=>
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem Stück Code zu, was es bewirkt.

  5. Üben · Aufgabe 5 von 7

    App rendert Counter zweimal. Sie klicken zweimal auf den ersten Button. Was zeigen die beiden Buttons?

    function Counter() {
      const [count, setCount] = useState(0);
    
      function handleClick() {
        setCount(count + 1);
      }
    
      return <button onClick={handleClick}>{count}</button>;
    }
    
    function App() {
      return (
        <>
          <Counter />
          <Counter />
        </>
      );
    }
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Dieser Zähler verwendet eine gewöhnliche Variable statt State. Sie klicken dreimal. Was zeigt er?

    function Counter() {
      let count = 0;
    
      function handleClick() {
        count = count + 1;
      }
    
      return <button onClick={handleClick}>Clicked {count} times</button>;
    }
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe: ein Punktezähler. Schreiben Sie in main.jsx eine Komponente Score, die ein Prop player bekommt und ihre Punkte im State hält, beginnend bei 0. Sie rendert einen Button, der Spieler und Punkte zeigt, etwa X: 0, und pro Klick einen Punkt addiert. Der Default-Export App rendert zwei Scores, für X und für O. Führen Sie es aus, klicken Sie jeden Button ein paarmal und prüfen Sie, dass jeder seine eigenen Punkte behält.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Felder, die sich einen Klick merken

Das ist das Brett aus dem Tutorial auf react.dev nach "Making an interactive component". Square bekommt kein Prop value mehr; es hält seinen Wert im State, beginnend mit null, jedes Feld ist also anfangs leer. handleClick ruft setValue('X') auf, und React rendert dieses Square neu, das jetzt X zeigt. Der Lauf unten zeigt das Brett vor jedem Klick. Im Browser bleibt die Seite aktiv: Klicken Sie ein Feld an, zeigt es X, die anderen acht bleiben leer.

main.jsx

import { useState } from 'react';

function Square() {
  const [value, setValue] = useState(null);

  function handleClick() {
    setValue('X');
  }

  return (
    <button className="square" onClick={handleClick}>
      {value}
    </button>
  );
}

export default function Board() {
  return (
    <>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
    </>
  );
}

styles.css

.board-row {
  display: flex;
}

.square {
  width: 2.5rem;
  height: 2.5rem;
  margin: 0 -1px -1px 0;
  border: 1px solid #999;
  background: #fff;
  font-size: 1.5rem;
  font-weight: bold;
}

Ausführen mit

npm run dev

Ausgabe

<!-- #root -->
<div id="root"><div class="board-row"><button class="square"></button><button class="square"></button><button class="square"></button></div><div class="board-row"><button class="square"></button><button class="square"></button><button class="square"></button></div><div class="board-row"><button class="square"></button><button class="square"></button><button class="square"></button></div></div>
  • null zeigt nichts, deshalb ist jeder Button anfangs leer.
  • onClick erscheint nicht im HTML: React hängt den Handler selbst an.
  • Jedes Square hat seinen eigenen Wert. Damit die Felder abwechselnd X und O zeigen, muss der State in Board: Lektion B1.4.
  • Vor dem Snapshot wurde nichts ausgegeben: Die Komponenten enthalten kein console.log.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser React und den Compiler herunter, der JSX in JavaScript übersetzt (bis zu 2 MB), und speichert beides im Cache. Ihre Komponenten laufen in Ihrem Browser und bleiben auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Übungen

Übung 1 von 2

Ein Feld per Klick füllen

Lassen Sie jedes Feld sich einen Klick merken, wie im Tutorial. Deklarieren Sie in Square mit useState eine State-Variable value, beginnend mit null, zeigen Sie sie im Button und schreiben Sie ein handleClick, das sie auf 'X' setzt. Übergeben Sie handleClick an onClick des Buttons. Lassen Sie export vor Square stehen: Die Tests rendern es auch allein.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser React und den Compiler herunter, der JSX in JavaScript übersetzt (bis zu 2 MB), und speichert beides im Cache. Ihre Komponenten laufen in Ihrem Browser und bleiben auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Hinweise
  1. Hinweis 1

    Oben in Square: const [value, setValue] = useState(null);

  2. Hinweis 2

    Schreiben Sie in Square function handleClick() { setValue('X'); } und zeigen Sie {value} im Button.

  3. Hinweis 3

    Schreiben Sie onClick={handleClick}, ohne runde Klammern nach handleClick.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import { useState } from 'react';

export function Square() {
  const [value, setValue] = useState(null);

  function handleClick() {
    setValue('X');
  }

  return (
    <button className="square" onClick={handleClick}>
      {value}
    </button>
  );
}

export default function Board() {
  return (
    <>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
    </>
  );
}
Auf dem eigenen Computer ausführen

Installieren Sie React 19.3 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.jsx

import { useState } from 'react';

export function Square() {
  // TODO: keep a value in state, starting at null, show it, and set it to 'X' on click.
  return <button className="square"></button>;
}

export default function Board() {
  return (
    <>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
    </>
  );
}

main.test.jsx

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Board, {Square} from './main.jsx';

const labels = () => screen.getAllByRole('button').map((button) => button.textContent);

test('Alle neun Felder sind anfangs leer', () => {
  render(<Board />);
  assert.deepEqual(labels(), ['', '', '', '', '', '', '', '', ''], 'vor dem ersten Klick zeigen die Felder ' + JSON.stringify(labels()));
});

test('Ein Klick auf ein Feld zeigt X', async () => {
  const user = userEvent.setup();
  render(<Square />);
  await user.click(screen.getByRole('button'));
  const label = screen.getByRole('button').textContent;
  assert.equal(label, 'X', 'nach einem Klick zeigt das Feld ' + JSON.stringify(label));
});

test('Jedes Feld hat seinen eigenen State', async () => {
  const user = userEvent.setup();
  render(<Board />);
  await user.click(screen.getAllByRole('button')[4]);
  assert.deepEqual(labels(), ['', '', '', '', 'X', '', '', '', ''], 'nach einem Klick auf das mittlere Feld zeigen die Felder ' + JSON.stringify(labels()));
});

test('React gibt keine Warnungen aus', async () => {
  const warnings = [];
  const original = console.error;
  console.error = (...args) => warnings.push(args.join(' '));
  try {
    const user = userEvent.setup();
    render(<Board />);
    await user.click(screen.getAllByRole('button')[0]);
  } finally {
    console.error = original;
  }
  assert.deepEqual(warnings, [], 'console.error beim Rendern und Klicken');
});

styles.css

.board-row {
  display: flex;
}

.square {
  width: 2.5rem;
  height: 2.5rem;
  margin: 0 -1px -1px 0;
  border: 1px solid #999;
  background: #fff;
  font-size: 1.5rem;
  font-weight: bold;
}

Legen Sie main.jsx in einem Vite-React-Projekt (npm create vite@latest, Vorlage react) als App.jsx in src/ ab und starten Sie es mit npm run dev. React rendert es dort wie hier in <StrictMode>.

Programm ausführen:

npm run dev

Für die Prüfungen gibt es noch keinen Befehl auf Ihrem Computer. Sie stehen in main.test.jsx, geschrieben mit node:test und React Testing Library.

Übung 2 von 2

Zähler, die getrennt zählen

Das ist das Beispiel aus dem Quick Start auf react.dev mit einem Fehler: MyButton zählt in einer gewöhnlichen Variablen, die Buttons zeigen also immer Clicked 0 times. Halten Sie den Zähler stattdessen im State, sodass jeder Klick eins addiert und jeder Button für sich zählt.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser React und den Compiler herunter, der JSX in JavaScript übersetzt (bis zu 2 MB), und speichert beides im Cache. Ihre Komponenten laufen in Ihrem Browser und bleiben auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Hinweise
  1. Hinweis 1

    Führen Sie es aus und klicken Sie: Die Variable ändert sich, aber nichts wird neu gerendert.

  2. Hinweis 2

    Ersetzen Sie let count = 0 durch const [count, setCount] = useState(0);

  3. Hinweis 3

    Rufen Sie in handleClick setCount(count + 1) auf, statt count zu ändern.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import { useState } from 'react';

function MyButton() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }

  return <button onClick={handleClick}>Clicked {count} times</button>;
}

export default function MyApp() {
  return (
    <div>
      <h1>Counters that update separately</h1>
      <MyButton />
      <MyButton />
    </div>
  );
}
Auf dem eigenen Computer ausführen

Installieren Sie React 19.3 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.jsx

import { useState } from 'react';

function MyButton() {
  // TODO: a plain variable does not update the screen; keep count in state.
  let count = 0;

  function handleClick() {
    count = count + 1;
  }

  return <button onClick={handleClick}>Clicked {count} times</button>;
}

export default function MyApp() {
  return (
    <div>
      <h1>Counters that update separately</h1>
      <MyButton />
      <MyButton />
    </div>
  );
}

main.test.jsx

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import MyApp from './main.jsx';

const labels = () => screen.getAllByRole('button').map((button) => button.textContent);

test('Beide Buttons beginnen mit Clicked 0 times', () => {
  render(<MyApp />);
  assert.deepEqual(labels(), ['Clicked 0 times', 'Clicked 0 times'], 'die Buttons zeigen ' + JSON.stringify(labels()));
});

test('Drei Klicks auf den ersten Button zeigen Clicked 3 times', async () => {
  const user = userEvent.setup();
  render(<MyApp />);
  for (let i = 0; i < 3; i++) await user.click(screen.getAllByRole('button')[0]);
  assert.equal(labels()[0], 'Clicked 3 times', 'nach drei Klicks zeigt der erste Button ' + JSON.stringify(labels()[0]));
});

test('Jeder Button zählt für sich', async () => {
  const user = userEvent.setup();
  render(<MyApp />);
  const [first, second] = screen.getAllByRole('button');
  await user.click(first);
  await user.click(first);
  await user.click(second);
  assert.deepEqual(labels(), ['Clicked 2 times', 'Clicked 1 times'], 'nach zwei Klicks auf den ersten und einem auf den zweiten zeigen die Buttons ' + JSON.stringify(labels()));
});

test('React gibt keine Warnungen aus', async () => {
  const warnings = [];
  const original = console.error;
  console.error = (...args) => warnings.push(args.join(' '));
  try {
    const user = userEvent.setup();
    render(<MyApp />);
    await user.click(screen.getAllByRole('button')[0]);
  } finally {
    console.error = original;
  }
  assert.deepEqual(warnings, [], 'console.error beim Rendern und Klicken');
});

Legen Sie main.jsx in einem Vite-React-Projekt (npm create vite@latest, Vorlage react) als App.jsx in src/ ab und starten Sie es mit npm run dev. React rendert es dort wie hier in <StrictMode>.

Programm ausführen:

npm run dev

Für die Prüfungen gibt es noch keinen Befehl auf Ihrem Computer. Sie stehen in main.test.jsx, geschrieben mit node:test und React Testing Library.

Häufige Fehler

Den Handler in onClick aufrufen

import { useState } from 'react';

export default function Square() {
  const [value, setValue] = useState(null);

  function handleClick() {
    setValue('X');
  }

  return <button onClick={handleClick()}>{value}</button>;
}

Was React ausgibt

Too many re-renders. React limits the number of renders to prevent an infinite loop.

Warum, und die Lösung

Die runden Klammern rufen handleClick auf, während Square rendert. Es setzt State, das rendert Square neu, das ruft handleClick wieder auf, und React beendet die Schleife mit Uncaught Error. Übergeben Sie stattdessen die Funktion: onClick={handleClick}.

Den Import von useState vergessen

export default function Square() {
  const [value, setValue] = useState(null);

  function handleClick() {
    setValue('X');
  }

  return <button onClick={handleClick}>{value}</button>;
}

Was React ausgibt

ReferenceError: useState is not defined

Warum, und die Lösung

useState kommt aus dem Paket react, die Datei muss es also importieren: import { useState } from 'react'; ganz oben. Der Lauf bricht mit Uncaught ReferenceError ab, sobald Square rendert.

useState außerhalb einer Komponente aufrufen

import { useState } from 'react';

const [value, setValue] = useState(null);

export default function Square() {
  return <button>{value}</button>;
}

Was React ausgibt

Invalid hook call. Hooks can only be called inside of the body of a function component.

Warum, und die Lösung

Hooks gehören oben in eine Komponente, nicht oben in die Datei. Hier läuft useState beim Laden der Datei, bevor React etwas rendert, deshalb warnt React, und der Lauf bricht mit TypeError: Cannot read properties of null (reading 'useState') ab. Verschieben Sie die Zeile in Square, vor alles andere.

React im Browser: React 19.3.0 (Entwicklungs-Build) und React Testing Library, MIT, von dieser Website ausgeliefert; JSX übersetzt TypeScript 6.0.3, Apache-2.0. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Den Handler übergeben, nicht aufrufen

Um auf einen Klick zu reagieren, deklarieren Sie in Ihrer Komponente eine Funktion, etwa handleClick, und übergeben sie dem Button: <button onClick={handleClick}>. Nach handleClick stehen keine runden Klammern: Sie geben React die Funktion, und React ruft sie auf, wenn jemand klickt. Mit Klammern, onClick={handleClick()}, läuft die Funktion schon beim Rendern, bevor jemand klickt. Setzt sie State, folgt ein neues Rendern, das sie wieder aufruft, und React bricht mit einem Fehler ab: Too many re-renders.

useState liefert einen Wert und einen Setter

Eine gewöhnliche Variable kann sich keinen Klick merken: let count = 0 beginnt bei jedem Durchlauf der Komponente wieder bei 0, und eine Änderung aktualisiert die Anzeige nicht. State kann das. Importieren Sie useState aus react und rufen Sie es oben in der Komponente auf: const [value, setValue] = useState(null). Sie bekommen den aktuellen Wert und eine Funktion, die ihn ändert. null ist der Startwert für das erste Rendern. Der Aufruf setValue('X') lässt React die Komponente neu rendern; diesmal liefert useState X, und die Anzeige zeigt es.

Jede Instanz hat eigenen State; Hooks stehen oben

Neun <Square />-Tags sind neun getrennte Felder, und jedes merkt sich seinen eigenen Wert: Ein Klick ändert nur dieses eine. State zwischen ihnen zu teilen ist Thema von Lektion B1.4. Funktionen, die mit use beginnen, etwa useState, sind Hooks. Rufen Sie sie nur oben in einer Komponente auf, nie in einem if, einer Schleife oder einem Event-Handler. React ordnet jeden useState-Aufruf seinem State über die Reihenfolge zu, deshalb muss sie bei jedem Rendern gleich sein. Brauchen Sie State in einer Bedingung, lagern Sie eine neue Komponente aus.

Quellen

Zuletzt geprüft am 5. Oktober 2026