Warum Index als Key oft falsch ist
Der Array-Index als key-Wert funktioniert scheinbar problemlos, bis eine Liste sortiert, gefiltert oder verändert wird. Dann recycelt React internen Komponenten-State zwischen den falschen Elementen, mit teils gravierenden, schwer nachvollziehbaren Folgen.
Inhaltsverzeichnis
- 1. Wozu der key-Prop eigentlich dient
- 2. Index als key: die naheliegende, aber trügerische Lösung
- 3. Konkretes Bug-Beispiel: Formularfelder in einer Todo-Liste
- 4. Die Lösung: stabile, inhaltliche IDs als key
- 5. Ein zweites Beispiel: Animationen und CSS-Transitions
- 6. Die Ausnahme: statische, niemals veränderte Listen
- 7. Ein häufiger Folgefehler: UUID bei jedem Render neu generieren
- 8. Debugging-Strategie: React DevTools und Symptome erkennen
- 9. Praxis-Zusammenfassung für den täglichen Gebrauch
- 10. Zusammenfassung
- 11. FAQ
1. Wozu der key-Prop eigentlich dient
React nutzt den key-Prop, um Elemente in einer Liste über mehrere Renders hinweg eindeutig zu identifizieren. Beim Vergleich des vorherigen mit dem neuen virtuellen DOM-Baum, der sogenannten Reconciliation, ordnet React jedes Element im neuen Baum anhand seines key-Werts einem Element im alten Baum zu. Stimmt der key überein, geht React davon aus, dass es sich um dieselbe logische Instanz handelt, und behält den internen State sowie den DOM-Knoten dieser Instanz bei, statt sie neu zu erzeugen.
Ohne stabile key-Werte kann React Elemente nicht zuverlässig über Renders hinweg identifizieren und greift auf Positions-basierte Heuristiken zurück, die schnell zu falschen Zuordnungen führen. Der key ist also kein reines Performance-Hinweis, sondern ein Identitätsmerkmal, das direkt beeinflusst, welche Komponenten-Instanz welchen State behält.
2. Index als key: die naheliegende, aber trügerische Lösung
Wenn eine Liste keine offensichtliche eindeutige ID besitzt, greifen viele Entwickler intuitiv zum Array-Index als key, weil er immer verfügbar und garantiert eindeutig innerhalb des aktuellen Renders ist. Solange sich die Reihenfolge und Zusammensetzung der Liste nie ändert, funktioniert das auch tatsächlich unauffällig, weil Index und logische Identität in diesem Fall zufällig übereinstimmen.
Das Problem entsteht, sobald sich die Liste verändert: Wird ein Element am Anfang eingefügt, gelöscht oder die Reihenfolge vertauscht, verschiebt sich der Index jedes nachfolgenden Elements, obwohl die eigentliche logische Identität der Elemente gleich geblieben ist. React sieht dann an Index 0 ein 'anderes' Element als vorher, obwohl dort inhaltlich dasselbe Element mit verschobener Position steht, und ordnet React-internen State entsprechend falsch zu.
3. Konkretes Bug-Beispiel: Formularfelder in einer Todo-Liste
Ein besonders anschauliches Beispiel ist eine Todo-Liste, bei der jedes Element ein eigenes, unkontrolliertes Eingabefeld für Notizen enthält. Löscht man das erste Element aus der Liste, während Index als key verwendet wird, behält React fälschlicherweise den DOM-Knoten und den internen State an Index 0 bei, weil React glaubt, es handle sich weiterhin um dieselbe Komponente. Der eingegebene Notiztext bleibt im Eingabefeld stehen, gehört inhaltlich aber jetzt zum falschen Todo-Eintrag.
Dieser Effekt ist deshalb so tückisch, weil er sich nicht als Absturz oder Fehlermeldung zeigt, sondern als stille, semantisch falsche Datenanzeige. Nutzer bemerken oft erst spät, dass eine Notiz plötzlich am falschen Eintrag hängt, und der Fehler lässt sich ohne Wissen um die Reconciliation-Mechanik nur schwer nachvollziehen.
function TodoList({ todos, onDelete }) {
return (
<ul>
{todos.map((todo, index) => (
// FALSCH: index als key -- Notiz-State wird beim Loeschen
// dem falschen Todo zugeordnet
<li key={index}>
<span>{todo.title}</span>
<input type="text" placeholder="Notiz..." />
<button onClick={() => onDelete(todo.id)}>Loeschen</button>
</li>
))}
</ul>
);
}
4. Die Lösung: stabile, inhaltliche IDs als key
Der korrekte Ansatz ist, eine stabile, inhaltlich verankerte ID zu verwenden, typischerweise eine Datenbank-ID oder eine beim Erzeugen des Elements einmalig generierte UUID. Diese ID bleibt über die gesamte Lebensdauer des logischen Elements unverändert, unabhängig davon, an welcher Position es sich in der Liste gerade befindet, und React kann dadurch Elemente auch nach Umsortierung, Filterung oder Löschung korrekt identifizieren.
Mit einer stabilen ID als key bleibt im obigen Todo-Beispiel der Notiztext korrekt am ursprünglichen Todo-Eintrag hängen, selbst wenn ein anderes Element aus der Liste entfernt wird. React erkennt anhand der ID zuverlässig, welche Komponenten-Instanz gelöscht wurde und welche unverändert an ihrer neuen Position weiterexistiert.
function TodoList({ todos, onDelete }) {
return (
<ul>
{todos.map((todo) => (
// RICHTIG: stabile todo.id als key
<li key={todo.id}>
<span>{todo.title}</span>
<input type="text" placeholder="Notiz..." />
<button onClick={() => onDelete(todo.id)}>Loeschen</button>
</li>
))}
</ul>
);
}
5. Ein zweites Beispiel: Animationen und CSS-Transitions
Ähnliche Probleme treten bei Listen mit Animationsbibliotheken auf, die auf Mount- und Unmount-Ereignisse einzelner Elemente reagieren. Verwendet eine sortierbare Liste den Index als key, erkennt die Animationsbibliothek beim Umsortieren keine tatsächliche Bewegung eines Elements, sondern nur eine inhaltliche Änderung an derselben Position, weil aus React-Sicht formal dieselbe Komponenten-Instanz an Index 0 bestehen bleibt.
Das Ergebnis ist, dass Elemente beim Umsortieren nicht sichtbar von Position A nach Position B animieren, sondern lediglich ihr Textinhalt an Ort und Stelle wechselt, was für Nutzer verwirrend wirkt und den Zweck der Animation komplett verfehlt. Mit einer stabilen ID als key erkennt React dagegen, dass sich eine bestehende Komponenten-Instanz nur an eine andere Position im Baum verschoben hat, wodurch Animationsbibliotheken den Übergang korrekt abbilden können.
6. Die Ausnahme: statische, niemals veränderte Listen
Index als key ist nicht in jedem Fall problematisch. Wenn eine Liste garantiert statisch ist, also niemals sortiert, gefiltert, eingefügt oder gelöscht wird und die Elemente selbst keinen internen State besitzen, etwa reine Anzeige-Texte ohne Formularfelder, ist der Index als key unbedenklich, weil sich die Zuordnung zwischen Index und logischer Identität nie ändert.
Ein Beispiel wäre eine feste Liste von Monatsnamen oder Navigationseinträgen, die zur Laufzeit konstant bleibt. In solchen Fällen ist der Aufwand, künstliche IDs einzuführen, unnötig, weil kein realistisches Szenario existiert, in dem die Positions-Index-Zuordnung bricht. Sobald aber auch nur die theoretische Möglichkeit einer Umsortierung oder Filterung besteht, sollte man von Anfang an eine stabile ID verwenden, um spätere, schwer auffindbare Bugs zu vermeiden.
7. Ein häufiger Folgefehler: UUID bei jedem Render neu generieren
Ein verwandter Fehler entsteht, wenn Entwickler versuchen, das Index-Problem zu vermeiden, aber die ID direkt im render-Aufruf mit crypto.randomUUID() neu generieren, statt sie einmalig beim Erzeugen der Daten festzulegen. Dadurch ändert sich der key-Wert bei jedem Render, React erkennt jedes Element als komplett neu und mountet die gesamte Liste bei jeder Aktualisierung neu, was jeglichen internen State verliert und die Performance verschlechtert.
Die korrekte Stelle für die ID-Generierung ist der Zeitpunkt, an dem das Datenobjekt erstellt wird, etwa beim Hinzufügen eines neuen Todo-Eintrags oder beim Laden der Daten von einer API, niemals während des Renderings selbst. Kommt die ID von einem Backend, ist ohnehin meist bereits eine stabile Datenbank-ID vorhanden, die sich direkt als key eignet.
// FALSCH: neue UUID bei jedem Render
function TodoList({ todos }) {
return todos.map((todo) => (
<TodoItem key={crypto.randomUUID()} todo={todo} />
));
}
// RICHTIG: ID einmalig beim Erzeugen des Datensatzes festlegen
function addTodo(title) {
return { id: crypto.randomUUID(), title, done: false };
}
8. Debugging-Strategie: React DevTools und Symptome erkennen
Typische Symptome für falsche key-Werte sind Eingabefelder, die nach dem Löschen oder Sortieren eines Listenelements den falschen Inhalt zeigen, Checkbox- oder Toggle-Zustände, die an der falschen Zeile hängen bleiben, oder Animationen, die beim Umsortieren nicht wie erwartet greifen. React gibt zusätzlich eine Warnung in der Browser-Konsole aus, wenn Listenelemente ganz ohne key-Prop gerendert werden, allerdings nicht, wenn ein technisch vorhandener, aber ungeeigneter Index als key genutzt wird.
In den React DevTools lässt sich das Verhalten gezielt nachvollziehen, indem man ein Listenelement inspiziert, es im UI löscht oder umsortiert und beobachtet, ob die DOM-Knoten tatsächlich neu erzeugt oder nur verschoben werden. Bleibt ein DOM-Knoten trotz gelöschten logischen Elements bestehen, ist das ein klares Indiz für einen fehlerhaften key.
9. Praxis-Zusammenfassung für den täglichen Gebrauch
Die generelle Empfehlung lautet, standardmäßig eine stabile, inhaltlich verankerte ID zu verwenden, sobald Elemente einer Liste eigenen State besitzen können oder theoretisch umsortiert, gefiltert, eingefügt oder gelöscht werden könnten. Nur bei nachweislich statischen, state-losen Listen ist der Index eine legitime, einfache Alternative.
Die folgende Tabelle fasst die wichtigsten Szenarien zusammen und zeigt, welche key-Strategie jeweils angebracht ist, damit die Entscheidung im Alltag nicht jedes Mal neu durchdacht werden muss.
| Szenario | Index als key | Stabile ID als key | Empfehlung |
|---|---|---|---|
| Statische Liste ohne internen State | unbedenklich | auch möglich | Index ausreichend |
| Liste mit Formularfeldern pro Element | State-Recycling-Bugs | korrekt | immer stabile ID |
| Liste mit Löschen/Einfügen | falsche Zuordnung | korrekt | immer stabile ID |
| Liste mit Sortier-Animation | Animation greift nicht | korrekt | immer stabile ID |
Mironsoft
React-Architektur, Performance und Magento-Frontend-Integration
React-Frontends, die schnell bleiben statt mit jedem Feature langsamer zu werden?
Wir prüfen bestehende React-Anwendungen auf unnötige Re-Renders, aufgeblähte Bundles und fragile State-Verwaltung und bauen daraus ein Frontend, das performant bleibt und sich sauber an Magento oder andere Backends anbindet.
Performance-Audit
Re-Renders, Bundle-Größe und Ladezeiten systematisch messen und beheben.
State-Architektur
Context, Zustand und Server State sauber trennen statt alles in einen Topf zu werfen.
Magento-Integration
GraphQL- oder REST-Anbindung an Magento robust und typsicher aufbauen.
10. Zusammenfassung
key-Prop: Das Wichtigste auf einen Blick
Kernproblem
Index als key koppelt Komponenten-Identität an Position statt an logische Identität.
Typischer Bug
State wie Eingabefeld-Inhalte bleibt nach Löschen/Sortieren am falschen Element hängen.
Korrekte Lösung
Stabile, inhaltlich verankerte ID beim Erzeugen des Datensatzes festlegen.
Erlaubte Ausnahme
Garantiert statische Listen ohne internen State pro Element.