Zum Inhalt springen
aviral gupta

// B5.4 · ca. 30 Min. · Einstieg

State erhalten und zurücksetzen

Nach dieser Lektion können Sie voraussagen, wann React den State einer Komponente behält und wann es ihn verwirft, und ihn gezielt mit einem key zurücksetzen.

Lektion 4 von 6 in B5 State verwalten

Danach können Sie

  • Erklären, dass React State an eine Position im Render-Baum bindet, nicht an ein JSX-Tag
  • Voraussagen, wann eine Änderung den State behält und wann ein anderer Typ oder Aufbau ihn zurücksetzt
  • State gezielt mit verschiedenen Positionen oder einem key zurücksetzen und Komponenten auf oberster Ebene definieren
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus Lektion B5.3: angehobener State. Panel besitzt count und gibt es an zwei Badges weiter. Sie klicken zweimal auf Add. Was zeigen die beiden <p>?

    function Badge({ count }) {
      return <p>Count: {count}</p>;
    }
    
    function Panel() {
      const [count, setCount] = useState(0);
      return (
        <>
          <Badge count={count} />
          <Badge count={count} />
          <button onClick={() => setCount(count + 1)}>Add</button>
        </>
      );
    }
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie voraus, bevor Sie weiterlesen. Mia klickt zweimal auf Add one, dann klickt jemand auf Next player. Was zeigt die Überschrift?

    function Counter({ person }) {
      const [score, setScore] = useState(0);
      return (
        <>
          <h1>{person}'s score: {score}</h1>
          <button onClick={() => setScore(score + 1)}>Add one</button>
        </>
      );
    }
    
    function Scoreboard() {
      const [isPlayerA, setIsPlayerA] = useState(true);
      return (
        <div>
          {isPlayerA ? <Counter person="Mia" /> : <Counter person="Leo" />}
          <button onClick={() => setIsPlayerA(!isPlayerA)}>Next player</button>
        </div>
      );
    }
  3. Üben · Aufgabe 3 von 7

    Scoreboard berechnet const person = isPlayerA ? 'Mia' : 'Leo'; und rendert einen Counter. Ergänzen Sie das Attribut, das jedem Spieler einen frischen Punktestand gibt.

    <Counter ____={person} person={person} />
    <Counter ={person} person={person} />
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jeder Änderung an einer Position zu, was mit dem State dort geschieht.

  5. Üben · Aufgabe 5 von 7

    App hat zwei return-Anweisungen. Sie klicken zweimal auf den Zähler, dann auf Fancy. Was zeigen die Buttons?

    function Counter() {
      const [score, setScore] = useState(0);
      return <button onClick={() => setScore(score + 1)}>Score: {score}</button>;
    }
    
    function App() {
      const [isFancy, setIsFancy] = useState(false);
      if (isFancy) {
        return (
          <div>
            <Counter />
            <button onClick={() => setIsFancy(false)}>Plain</button>
          </div>
        );
      }
      return (
        <div>
          <Counter />
          <button onClick={() => setIsFancy(true)}>Fancy</button>
        </div>
      );
    }
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. NameField ist in Form deklariert. Sie tippen Ada in das Feld und klicken dann auf den Button. Was zeigen Feld und Button?

    function Form() {
      const [count, setCount] = useState(0);
    
      function NameField() {
        const [text, setText] = useState('');
        return <input value={text} onChange={e => setText(e.target.value)} />;
      }
    
      return (
        <>
          <NameField />
          <button onClick={() => setCount(count + 1)}>Clicked {count} times</button>
        </>
      );
    }
  7. Anwenden · Aufgabe 7 von 7

    Kleine Aufgabe: ein Quiz, das jede Frage mit einer leeren Antwort beginnt. Schreiben Sie in main.jsx eine Komponente Answer, die den Text einer Frage zeigt und ein Eingabefeld, dessen Wert ihr eigener State answer ist. Schreiben Sie eine Komponente Quiz, die den Index der aktuellen Frage im State hält, ein Answer dafür rendert und einen Button Next question. Ohne key bliebe die getippte Antwort für die nächste Frage stehen; geben Sie Answer einen key, damit jede Frage leer beginnt.

    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

Eine Anzeigetafel, auf der jeder Spieler bei null beginnt

Die beiden Counter stehen an derselben Stelle im JSX, aber jeder hat seinen eigenen key, Mia oder Leo. React nutzt den key als Teil der Position, also entfernt ein Spielerwechsel einen Counter samt State und erzeugt den anderen von vorn. Ein Lauf hängt die Komponente nur ein, also klicken Timer unten in main.jsx die Buttons, alle 50 ms ein Schritt, und geben nach jeder Änderung die Überschrift aus. Im Browser bleibt die Seite aktiv: Klicken Sie selbst.

main.jsx

import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA ? (
        <Counter key="Mia" person="Mia" />
      ) : (
        <Counter key="Leo" person="Leo" />
      )}
      <button onClick={() => setIsPlayerA(!isPlayerA)}>Next player</button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  return (
    <section>
      <h1>{person}'s score: {score}</h1>
      <button onClick={() => setScore(score + 1)}>Add one</button>
    </section>
  );
}

// A run only mounts. Act like the players, one step every 50 ms.
const click = name => [...document.querySelectorAll('button')].find(b => b.textContent === name).click();
const print = () => console.log(document.querySelector('h1').textContent);
const plan = [
  () => click('Add one'),
  () => click('Add one'),
  () => click('Add one'),
  print,
  () => click('Next player'),
  print,
  () => click('Add one'),
  print,
  () => click('Next player'),
  print,
];
plan.forEach((step, i) => setTimeout(step, 50 * (i + 1)));

Ausführen mit

npm run dev

Ausgabe

Mia's score: 3
Leo's score: 0
Leo's score: 1
Mia's score: 0
<!-- #root -->
<div id="root"><div><section><h1>Mia's score: 0</h1><button>Add one</button></section><button>Next player</button></div></div>
  • Mia erzielte 3 Punkte. Next player entfernte ihren Counter und damit seinen State.
  • Leos Counter begann bei 0, obwohl er an derselben Stelle im JSX rendert.
  • Der Wechsel zurück erzeugte Mias Counter neu, von vorn: 0, nicht 3. Ihren Punktestand zu behalten hieße, ihn in Scoreboard anzuheben.
  • Ohne die keys behielte der ternäre Operator einen Punktestand für beide Spieler, wie in der Aktivität davor.
Ä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

Den Entwurf leeren, wenn der Empfänger wechselt

Messenger zeigt eine ContactList und einen Chat für den gewählten Kontakt. Tippen Sie eine Nachricht an Mia und wählen Sie dann Leo: Der Entwurf bleibt, also könnten Sie Mias Nachricht versehentlich an Leo schicken. Chat ist dieselbe Komponente an derselben Position, also behält React ihren State. Sorgen Sie dafür, dass React für jeden Kontakt einen neuen Chat erzeugt, sodass ein Wechsel immer mit einer leeren Nachricht beginnt. Ändern Sie nur, wie Messenger Chat rendert.

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

    Ein key sagt React, dass zwei Komponenten an derselben Position verschieden sind.

  2. Hinweis 2

    Nehmen Sie etwas, das den Kontakt eindeutig bezeichnet, etwa seine id.

  3. Hinweis 3

    <Chat key={to.id} contact={to} />

Eine Lösung zeigen

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

import { useState } from 'react';

const contacts = [
  { id: 0, name: 'Mia', email: 'mia@mail.com' },
  { id: 1, name: 'Leo', email: 'leo@mail.com' },
  { id: 2, name: 'Noor', email: 'noor@mail.com' }
];

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat key={to.id} contact={to} />
    </div>
  );
}

function ContactList({ contacts, selectedContact, onSelect }) {
  return (
    <ul>
      {contacts.map(contact => (
        <li key={contact.id}>
          <button
            aria-pressed={contact.id === selectedContact.id}
            onClick={() => onSelect(contact)}
          >
            {contact.name}
          </button>
        </li>
      ))}
    </ul>
  );
}

function Chat({ contact }) {
  const [text, setText] = useState('');
  return (
    <section>
      <textarea
        aria-label={'Message to ' + contact.name}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button>Send to {contact.email}</button>
    </section>
  );
}
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';

const contacts = [
  { id: 0, name: 'Mia', email: 'mia@mail.com' },
  { id: 1, name: 'Leo', email: 'leo@mail.com' },
  { id: 2, name: 'Noor', email: 'noor@mail.com' }
];

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat contact={to} />
    </div>
  );
}

function ContactList({ contacts, selectedContact, onSelect }) {
  return (
    <ul>
      {contacts.map(contact => (
        <li key={contact.id}>
          <button
            aria-pressed={contact.id === selectedContact.id}
            onClick={() => onSelect(contact)}
          >
            {contact.name}
          </button>
        </li>
      ))}
    </ul>
  );
}

function Chat({ contact }) {
  const [text, setText] = useState('');
  return (
    <section>
      <textarea
        aria-label={'Message to ' + contact.name}
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button>Send to {contact.email}</button>
    </section>
  );
}

main.test.jsx

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen, fireEvent} from '@testing-library/react';
import Messenger from './main.jsx';

const message = (name) => screen.getByRole('textbox', {name: 'Message to ' + name});
const contact = (name) => screen.getByRole('button', {name});

test('Eine getippte Nachricht bleibt, solange der Kontakt bleibt', () => {
  render(<Messenger />);
  fireEvent.change(message('Mia'), {target: {value: 'Hi Mia'}});
  assert.equal(message('Mia').value, 'Hi Mia', 'die Nachricht an Mia enthält ' + JSON.stringify(message('Mia').value));
});

test('Die Wahl eines anderen Kontakts leert den Entwurf', () => {
  render(<Messenger />);
  fireEvent.change(message('Mia'), {target: {value: 'Hi Mia'}});
  fireEvent.click(contact('Leo'));
  assert.equal(message('Leo').value, '', 'nach der Wahl von Leo enthält die Nachricht ' + JSON.stringify(message('Leo').value));
});

test('Jeder Wechsel beginnt mit einer leeren Nachricht', () => {
  render(<Messenger />);
  fireEvent.click(contact('Noor'));
  fireEvent.change(message('Noor'), {target: {value: 'Lunch?'}});
  fireEvent.click(contact('Mia'));
  assert.equal(message('Mia').value, '', 'nach dem Wechsel von Noor zu Mia enthält die Nachricht ' + JSON.stringify(message('Mia').value));
  assert.equal(screen.getByRole('button', {name: /^Send to/}).textContent, 'Send to mia@mail.com', 'der Senden-Button lautet ' + JSON.stringify(screen.getByRole('button', {name: /^Send to/}).textContent));
});

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

Die Punkte bei ihren Spielern halten

Scoreboard zeigt Mia und Leo, und Swap ändert ihre Reihenfolge. Geben Sie Mia zwei Punkte und klicken Sie auf Swap: Jetzt hat Leo ihre 2 Punkte und Mia 0, weil der State an seiner Position bleibt, während die Personen wandern. Sorgen Sie dafür, dass jeder Punktestand bei seinem Spieler bleibt, wie oft Sie auch tauschen. Behalten Sie die beiden Zweige und den Button Swap.

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

    Ohne keys ordnet React die beiden Counter nach ihrer Reihenfolge im Elternteil zu.

  2. Hinweis 2

    Keys gibt es nicht nur für Listen: Sie wirken bei allen Komponenten mit demselben Elternteil.

  3. Hinweis 3

    Geben Sie jedem Counter seinen Spieler als key, in beiden Zweigen: <Counter key="Mia" person="Mia" />.

Eine Lösung zeigen

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

import { useState } from 'react';

export default function Scoreboard() {
  const [reversed, setReversed] = useState(false);
  return (
    <>
      {reversed ? (
        <>
          <Counter key="Leo" person="Leo" />
          <Counter key="Mia" person="Mia" />
        </>
      ) : (
        <>
          <Counter key="Mia" person="Mia" />
          <Counter key="Leo" person="Leo" />
        </>
      )}
      <button onClick={() => setReversed(!reversed)}>Swap</button>
    </>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  return (
    <section>
      <h2>{person}: {score}</h2>
      <button aria-label={'Add one for ' + person} onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </section>
  );
}
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 default function Scoreboard() {
  const [reversed, setReversed] = useState(false);
  return (
    <>
      {reversed ? (
        <>
          <Counter person="Leo" />
          <Counter person="Mia" />
        </>
      ) : (
        <>
          <Counter person="Mia" />
          <Counter person="Leo" />
        </>
      )}
      <button onClick={() => setReversed(!reversed)}>Swap</button>
    </>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  return (
    <section>
      <h2>{person}: {score}</h2>
      <button aria-label={'Add one for ' + person} onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </section>
  );
}

main.test.jsx

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen, fireEvent} from '@testing-library/react';
import Scoreboard from './main.jsx';

const scores = () => screen.getAllByRole('heading').map((h) => h.textContent).join(', ');
const add = (person) => fireEvent.click(screen.getByRole('button', {name: 'Add one for ' + person}));
const swap = () => fireEvent.click(screen.getByRole('button', {name: 'Swap'}));

test('Jeder Zähler zählt seinen eigenen Punktestand', () => {
  render(<Scoreboard />);
  add('Mia');
  add('Mia');
  add('Leo');
  assert.equal(scores(), 'Mia: 2, Leo: 1', 'nach zwei Punkten für Mia und einem für Leo lauten die Punktestände ' + JSON.stringify(scores()));
});

test('Nach einem Tausch bleiben die Punkte bei ihren Spielern', () => {
  render(<Scoreboard />);
  add('Mia');
  add('Mia');
  swap();
  assert.equal(scores(), 'Leo: 0, Mia: 2', 'nach zwei Punkten für Mia und einem Tausch lauten die Punktestände ' + JSON.stringify(scores()));
});

test('Die Punkte überstehen zwei Tausche und weitere Klicks', () => {
  render(<Scoreboard />);
  add('Mia');
  add('Mia');
  swap();
  add('Leo');
  assert.equal(scores(), 'Leo: 1, Mia: 2', 'nach einem Tausch und einem Punkt für Leo lauten die Punktestände ' + JSON.stringify(scores()));
  swap();
  assert.equal(scores(), 'Mia: 2, Leo: 1', 'nach dem Zurücktauschen lauten die Punktestände ' + JSON.stringify(scores()));
});

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

Sich auf State des vorigen Kontakts verlassen

import { useState } from 'react';

const chats = {
  Mia: [
    { id: 0, text: 'See you at six?' },
    { id: 1, text: 'Bring the map.' },
    { id: 2, text: 'Thanks!' }
  ],
  Leo: [{ id: 0, text: 'Running late.' }]
};

export default function Messenger() {
  const [name, setName] = useState('Mia');
  return (
    <>
      <button onClick={() => setName('Mia')}>Mia</button>
      <button onClick={() => setName('Leo')}>Leo</button>
      <Thread messages={chats[name]} />
    </>
  );
}

function Thread({ messages }) {
  const [index, setIndex] = useState(0);
  return (
    <section>
      <p>{messages[index].text}</p>
      <button onClick={() => setIndex(index + 1)}>Older</button>
    </section>
  );
}

// A run only mounts. Act like a user: read Mia's third message, then open Leo.
setTimeout(() => [...document.querySelectorAll('button')].find(b => b.textContent === 'Older').click(), 50);
setTimeout(() => [...document.querySelectorAll('button')].find(b => b.textContent === 'Older').click(), 100);
setTimeout(() => [...document.querySelectorAll('button')].find(b => b.textContent === 'Leo').click(), 150);

Was React ausgibt

TypeError: Cannot read properties of undefined (reading 'text')

Warum, und die Lösung

Thread bleibt an derselben Position, also übersteht sein index 2 den Wechsel, und Leo hat nur eine Nachricht: messages[2] ist undefined (Wortlaut von Chrome und Node; Firefox und Safari formulieren ihn anders). Geben Sie jedem Gespräch einen eigenen Thread: <Thread key={name} messages={chats[name]} />, damit index wieder bei 0 beginnt.

Eine Komponente als Funktion aufrufen

import { useState } from 'react';

export default function Scoreboard() {
  const [showLeo, setShowLeo] = useState(false);
  return (
    <div>
      {Counter({ person: 'Mia' })}
      {showLeo && Counter({ person: 'Leo' })}
      <button onClick={() => setShowLeo(true)}>Add Leo</button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  return <h1>{person}: {score}</h1>;
}

// A run only mounts. Act like a user: add Leo.
setTimeout(() => [...document.querySelectorAll('button')].find(b => b.textContent === 'Add Leo').click(), 50);

Was React ausgibt

Rendered more hooks than during the previous render.

Warum, und die Lösung

Counter({ person: 'Mia' }) ist ein gewöhnlicher Funktionsaufruf, keine Komponente an einer Position, also gehört sein useState zu Scoreboard. Kommt Leo hinzu, bekommt Scoreboard einen Hook mehr, und React bricht ab. Rendern Sie Komponenten mit JSX, <Counter person="Mia" />, damit jede ihre eigene Position und ihren eigenen State bekommt.

Ein zurückgesetzter State, der mit einer fehlenden Prop beginnt

import { useState } from 'react';

const contacts = [
  { id: 0, name: 'Mia', draft: 'See you at six?' },
  { id: 1, name: 'Leo' }
];

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <>
      {contacts.map(contact => (
        <button key={contact.id} onClick={() => setTo(contact)}>
          {contact.name}
        </button>
      ))}
      <Chat key={to.id} contact={to} />
    </>
  );
}

function Chat({ contact }) {
  const [text, setText] = useState(contact.draft);
  return (
    <section>
      <textarea value={text} onChange={e => setText(e.target.value)} />
      <p>{text.length} / 280</p>
    </section>
  );
}

// A run only mounts. Act like a user: open Leo.
setTimeout(() => [...document.querySelectorAll('button')].find(b => b.textContent === 'Leo').click(), 50);

Was React ausgibt

TypeError: Cannot read properties of undefined (reading 'length')

Warum, und die Lösung

Der key wirkt: Die Wahl von Leo erzeugt einen neuen Chat, und sein useState läuft erneut. Aber Leo hat keinen draft, also beginnt text mit undefined, und text.length wirft (Wortlaut von Chrome und Node; Firefox und Safari formulieren ihn anders). Machen Sie den Anfangswert für jeden Kontakt gültig: useState(contact.draft ?? '').

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

State gehört zu einer Position im Baum

State steckt weder im JSX-Tag noch in der Komponentenfunktion. React hält ihn und bindet ihn an die Stelle, an der die Komponente im Render-Baum sitzt. Zwei <Counter /> nebeneinander sind zwei Positionen, also bekommt jeder seinen eigenen score, selbst wenn beide aus einer JSX-Variablen stammen. Wird eine Komponente nicht mehr gerendert, zerstört React ihren State; kommt sie zurück, beginnt ihr State von vorn. Solange dieselbe Komponente an derselben Position gerendert wird, behält React den State, welche Props sie auch bekommt. Auch zwei return-Anweisungen in einem if ergeben dieselbe Position, wenn der zurückgegebene Baum dieselbe Form hat.

Ein anderer Typ an derselben Position setzt den Teilbaum zurück

Ersetzt ein <p> den <Counter> an einer Position, entfernt React den Counter und zerstört seinen State. Dasselbe gilt für alles darunter: Wird eine <section>, die einen Counter enthält, zu einem <div>, wird der ganze Teilbaum neu aufgebaut, und der Counter beginnt bei 0. Damit State zwischen Renderdurchläufen bleibt, muss der Aufbau des Baums von einem Rendern zum nächsten übereinstimmen. Deshalb dürfen Komponentendefinitionen auch nicht verschachtelt werden: Eine Komponentenfunktion, die in einer anderen deklariert ist, ist bei jedem Rendern eine neue Funktion, also sieht React an dieser Position einen anderen Typ und setzt ihren State jedes Mal zurück.

Gezielt zurücksetzen: andere Positionen oder ein key

{isPlayerA ? <Counter person="Mia" /> : <Counter person="Leo" />} behält einen Punktestand für beide Spieler, weil beide dieselbe Komponente an derselben Position sind. Um ihn zurückzusetzen, rendern Sie sie an zwei Positionen, {isPlayerA && …}{!isPlayerA && …}, oder geben Sie jedem mit einem key eine Identität: <Counter key="Mia" … />. React nutzt den key dann als Teil der Position statt der Reihenfolge im Elternteil. Keys müssen nur unter Geschwistern eindeutig sein. Ein key ist der übliche Weg, ein Formular zurückzusetzen: <Chat key={to.id} contact={to} /> leert den Entwurf, wenn der Empfänger wechselt. Den Entwurf eines entfernten Chats zu behalten hieße, ihn anzuheben; das behandelt diese Lektion nicht.

Quellen

Zuletzt geprüft am 5. Oktober 2026