Zum Inhalt springen
aviral gupta

// B2.5 · ca. 30 Min. · Einstieg

Listen und Keys

Nach dieser Lektion machen Sie aus Datenarrays Listen mit map() und filter() und geben jedem Item einen Key, der es von den anderen unterscheidet.

Lektion 5 von 6 in B2 Die Oberfläche beschreiben

Danach können Sie

  • Ein Array mit map() als JSX rendern, vorher mit filter(), wenn nur einige Items gehören
  • Jedem Item einen eindeutigen, stabilen Key aus den Daten geben, am Element, das map liefert
  • Erklären, warum Index- und Zufalls-Keys Listen stören und dass key kein Prop ist
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus Lektion B2.4: der ternäre Operator und &&. Eine Elternkomponente rendert <Score points={0} />. Welchen Text zeigt es?

    function Score({ points }) {
      return (
        <p>
          {points > 0 ? points + ' points' : 'No points'}
          {points > 99 && ' 🏆'}
        </p>
      );
    }
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie voraus, bevor Sie weiterlesen. Sie klicken den Stern neben Ada an, dann Add Linus, das Linus oben einfügt. Welcher Name hat jetzt den gefüllten Stern ★ (nobody = niemand)?

    function Row({ name }) {
      const [starred, setStarred] = useState(false);
      return (
        <li>
          {name}
          <button onClick={() => setStarred(!starred)}>{starred ? '★' : '☆'}</button>
        </li>
      );
    }
    
    function List() {
      const [names, setNames] = useState(['Ada', 'Grace']);
      return (
        <>
          <button onClick={() => setNames(['Linus', ...names])}>Add Linus</button>
          <ul>
            {names.map((name, index) => <Row key={index} name={name} />)}
          </ul>
        </>
      );
    }
  3. Üben · Aufgabe 3 von 7

    Jede Person hat eine eindeutige id. Ergänzen Sie den Key, damit React jedem <li> folgen kann, wenn sich die Reihenfolge ändert.

    function List({ people }) {
      return (
        <ul>
          {people.map(person => <li key={____}>{person.name}</li>)}
        </ul>
      );
    }
    {people.map(person => <li key={}>{person.name}</li>)}
  4. Üben · Aufgabe 4 von 7

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

  5. Üben · Aufgabe 5 von 7

    Wie viele <li>-Elemente rendert FruitList?

    const fruits = ['Apple', 'Pear', 'Plum'];
    
    function FruitList() {
      const items = fruits.map(fruit => {
        <li key={fruit}>{fruit}</li>;
      });
      return <ul>{items}</ul>;
    }
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Der Key ist da, aber am <b> in jedem <li>. Was gibt React aus (Antworten auf Englisch: no warning = keine Warnung, a key warning = eine Key-Warnung)?

    const team = [
      { id: 1, name: 'Ada' },
      { id: 2, name: 'Grace' }
    ];
    
    function Team() {
      return (
        <ul>
          {team.map(person => (
            <li>
              <b key={person.id}>{person.name}</b>
            </li>
          ))}
        </ul>
      );
    }
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe: eine Leseliste. Legen Sie in main.jsx ein Array mit drei Büchern an, jedes mit id, title, author und einem read-Flag. Zeigen Sie nur die noch nicht gelesenen Bücher: filtern, dann jedes Buch auf ein <dt> mit dem Titel und ein <dd> mit dem Autor in einem <dl> mappen. Jedes Buch rendert zwei Elemente, also gruppieren Sie sie in einem Fragment mit der id des Buchs als Key (import { Fragment } from 'react'). Führen Sie es aus: Es darf keine Warnung erscheinen.

    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 Ladenliste aus Daten

Die Produkte stehen in data.js, jedes mit einer id, wie react.dev es empfiehlt. Shop filtert das Obst und mappt es auf <li>-Elemente mit key={product.id}. Die Preisliste mappt jedes Produkt auf zwei Elemente, ein <dt> und ein <dd>, gruppiert in einem Fragment, das den Key trägt. Dieser Callback hat einen Block-Body, weil er zuerst den Preis ausrechnet, und braucht deshalb sein eigenes return.

main.jsx

import { Fragment } from 'react';
import { products } from './data.js';

export default function Shop() {
  const fruits = products.filter(product => product.category === 'Fruit');
  const fruitItems = fruits.map(product => <li key={product.id}>{product.name}</li>);

  return (
    <section>
      <h1>Fruit</h1>
      <ul>{fruitItems}</ul>
      <h2>Prices</h2>
      <dl>
        {products.map(product => {
          const price = product.price.toFixed(2) + ' €';
          return (
            <Fragment key={product.id}>
              <dt>{product.name}</dt>
              <dd>{price}</dd>
            </Fragment>
          );
        })}
      </dl>
    </section>
  );
}

data.js

export const products = [
  { id: 'p1', name: 'Apple', category: 'Fruit', price: 0.5 },
  { id: 'p2', name: 'Carrot', category: 'Vegetable', price: 0.3 },
  { id: 'p3', name: 'Banana', category: 'Fruit', price: 0.25 },
  { id: 'p4', name: 'Leek', category: 'Vegetable', price: 1.2 }
];

Ausführen mit

npm run dev

Ausgabe

<!-- #root -->
<div id="root"><section><h1>Fruit</h1><ul><li>Apple</li><li>Banana</li></ul><h2>Prices</h2><dl><dt>Apple</dt><dd>0.50 €</dd><dt>Carrot</dt><dd>0.30 €</dd><dt>Banana</dt><dd>0.25 €</dd><dt>Leek</dt><dd>1.20 €</dd></dl></section></div>
  • filter() behielt Apple und Banana in ihrer ursprünglichen Reihenfolge, und map() machte je ein <li>.
  • Die Fragments hinterließen keine Spur: Das <dl> enthält eine flache Folge von <dt> und <dd>.
  • Im HTML erscheint kein Key; React behält Keys für sich.
  • Vor dem Snapshot wurde nichts ausgegeben, React gab also keine Key-Warnung.
Ä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

Nur die Engineers

Die Teamseite listet alle auf, und React warnt vor fehlenden Keys. Ändern Sie sie so, dass sie nur die Personen mit der Rolle Engineer zeigt, jede als <li> wie "Mira Okafor: Engineer", in der Reihenfolge aus data.js. Filtern Sie zuerst, mappen Sie dann, und geben Sie jedem <li> einen Key aus den Daten.

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

    people.filter(person => person.role === 'Engineer') gibt ein neues Array mit den drei Engineers.

  2. Hinweis 2

    Mappen Sie dieses Array statt people.

  3. Hinweis 3

    Jede Person hat eine id, also bekommt das <li> key={person.id}.

Eine Lösung zeigen

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

import { people } from './data.js';

export default function Engineers() {
  const engineers = people.filter(person => person.role === 'Engineer');
  const listItems = engineers.map(person => (
    <li key={person.id}>
      {person.name}: {person.role}
    </li>
  ));
  return (
    <section>
      <h1>Engineers</h1>
      <ul>{listItems}</ul>
    </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 { people } from './data.js';

// TODO: list only the engineers, each as <li>Name: role</li>,
// with a key that comes from the data.
export default function Engineers() {
  const listItems = people.map(person => <li>{person.name}: {person.role}</li>);
  return (
    <section>
      <h1>Engineers</h1>
      <ul>{listItems}</ul>
    </section>
  );
}

main.test.jsx

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

test('React gibt keine Warnungen aus', () => {
  const warnings = [];
  const original = console.error;
  console.error = (...args) => warnings.push(args.join(' '));
  try {
    render(<Engineers />);
  } finally {
    console.error = original;
  }
  assert.deepEqual(warnings, [], 'console.error beim Rendern');
});

test('Nur die drei Engineers stehen in der Liste, in ihrer Reihenfolge', () => {
  render(<Engineers />);
  const items = screen.getAllByRole('listitem').map((li) => li.textContent);
  assert.deepEqual(items, ['Mira Okafor: Engineer', 'Lena Wolf: Engineer', 'Ines Duarte: Engineer'], 'die Liste lautet ' + JSON.stringify(items));
});

data.js

export const people = [
  { id: 'u1', name: 'Mira Okafor', role: 'Engineer' },
  { id: 'u2', name: 'Tom Becker', role: 'Designer' },
  { id: 'u3', name: 'Lena Wolf', role: 'Engineer' },
  { id: 'u4', name: 'Sam Park', role: 'Manager' },
  { id: 'u5', name: 'Ines Duarte', role: 'Engineer' }
];

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

Sterne, die bleiben

Geben Sie Ada einen Stern und fügen Sie dann oben einen Gast hinzu: Der Stern springt zum neuen Gast, weil die Liste den Index als Key nimmt. Beheben Sie das: Geben Sie jedem neuen Gast in handleAdd, wo er angelegt wird, eine id (der Zähler nextNumber ist dafür da), und nehmen Sie die id als Key. Behalten Sie die Button-Texte; die Tests klicken sie.

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

    Der Stern-State gehört zu einem Guest, und React findet diesen Guest über seinen Key wieder. Index 0 ist nach einem Einfügen oben ein anderer Gast.

  2. Hinweis 2

    Ergänzen Sie in handleAdd eine id am neuen Objekt: { id: 'new' + nextNumber, name: 'Guest ' + nextNumber }.

  3. Hinweis 3

    Lassen Sie im map den index weg und schreiben Sie <Guest key={guest.id} name={guest.name} />.

Eine Lösung zeigen

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

import { useState } from 'react';

function Guest({ name }) {
  const [starred, setStarred] = useState(false);
  return (
    <li>
      {name}{' '}
      <button aria-pressed={starred} onClick={() => setStarred(!starred)}>
        Star {name}
      </button>
    </li>
  );
}

let nextNumber = 1;

export default function GuestList() {
  const [guests, setGuests] = useState([
    { id: 'g1', name: 'Ada' },
    { id: 'g2', name: 'Grace' }
  ]);

  function handleAdd() {
    const guest = { id: 'new' + nextNumber, name: 'Guest ' + nextNumber };
    nextNumber++;
    setGuests([guest, ...guests]);
  }

  return (
    <>
      <button onClick={handleAdd}>Add a guest at the top</button>
      <ul>
        {guests.map(guest => (
          <Guest key={guest.id} name={guest.name} />
        ))}
      </ul>
    </>
  );
}
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 Guest({ name }) {
  const [starred, setStarred] = useState(false);
  return (
    <li>
      {name}{' '}
      <button aria-pressed={starred} onClick={() => setStarred(!starred)}>
        Star {name}
      </button>
    </li>
  );
}

let nextNumber = 1;

export default function GuestList() {
  const [guests, setGuests] = useState([
    { id: 'g1', name: 'Ada' },
    { id: 'g2', name: 'Grace' }
  ]);

  function handleAdd() {
    // TODO: give the new guest an id here, where it is created.
    const guest = { name: 'Guest ' + nextNumber };
    nextNumber++;
    setGuests([guest, ...guests]);
  }

  return (
    <>
      <button onClick={handleAdd}>Add a guest at the top</button>
      <ul>
        {guests.map((guest, index) => (
          <Guest key={index} name={guest.name} />
        ))}
      </ul>
    </>
  );
}

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 GuestList from './main.jsx';

test('React gibt keine Warnungen aus, während zwei Gäste hinzukommen', async () => {
  const user = userEvent.setup();
  const warnings = [];
  const original = console.error;
  console.error = (...args) => warnings.push(args.join(' '));
  try {
    render(<GuestList />);
    await user.click(screen.getByRole('button', {name: 'Add a guest at the top'}));
    await user.click(screen.getByRole('button', {name: 'Add a guest at the top'}));
  } finally {
    console.error = original;
  }
  assert.deepEqual(warnings, [], 'console.error beim Hinzufügen der Gäste');
});

test('Ein neuer Gast erscheint oben', async () => {
  const user = userEvent.setup();
  render(<GuestList />);
  await user.click(screen.getByRole('button', {name: 'Add a guest at the top'}));
  const first = screen.getAllByRole('listitem')[0].textContent;
  assert.match(first, /^Guest/, 'das erste Item lautet ' + JSON.stringify(first));
});

test('Ada behält ihren Stern, wenn oben ein Gast hinzukommt', async () => {
  const user = userEvent.setup();
  render(<GuestList />);
  await user.click(screen.getByRole('button', {name: 'Star Ada'}));
  await user.click(screen.getByRole('button', {name: 'Add a guest at the top'}));
  const ada = screen.getByRole('button', {name: 'Star Ada'}).getAttribute('aria-pressed');
  assert.equal(ada, 'true', 'Ada hat keinen Stern mehr');
  const starred = screen.getAllByRole('button', {pressed: true}).map((button) => button.textContent);
  assert.deepEqual(starred, ['Star Ada'], 'Buttons mit Stern: ' + JSON.stringify(starred));
});

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

Zwei Items mit derselben id in den Daten

const fruits = [
  { id: 'f1', name: 'Apple' },
  { id: 'f2', name: 'Banana' },
  { id: 'f2', name: 'Cherry' }
];

export default function FruitList() {
  return (
    <ul>
      {fruits.map(fruit => <li key={fruit.id}>{fruit.name}</li>)}
    </ul>
  );
}

Was React ausgibt

Encountered two children with the same key, `f2`.

Warum, und die Lösung

Der Code stimmt, die Daten nicht: Cherry wurde von Banana kopiert und hat dessen id behalten. Keys müssen unter Geschwistern eindeutig sein, also warnt React, und bei Duplikaten kann es Items doppelt zeigen oder auslassen, wenn sich die Liste ändert. Korrigieren Sie die Daten (Cherry bekommt f3). Wenn Sie Items selbst anlegen, macht ein Zähler oder crypto.randomUUID() die id eindeutig.

key als Prop lesen

const fruits = [
  { id: 'f1', name: 'Apple' },
  { id: 'f2', name: 'Banana' }
];

function Fruit({ key, name }) {
  return <li>{key}: {name}</li>;
}

export default function FruitList() {
  return (
    <ul>
      {fruits.map(fruit => <Fruit key={fruit.id} name={fruit.name} />)}
    </ul>
  );
}

Was React ausgibt

`key` is not a prop. Trying to access it will result in `undefined` being returned.

Warum, und die Lösung

React behält den Key für sich und reicht ihn nicht weiter, also ist key in Fruit undefined, und die Items lauten ": Apple". Die Warnung beginnt mit dem Komponentennamen, Fruit:. Braucht die Komponente die id, übergeben Sie sie als zweites Prop: <Fruit key={fruit.id} id={fruit.id} name={fruit.name} />.

Fragment verwenden, ohne es zu importieren

const terms = [
  { id: 't1', word: 'JSX', meaning: 'markup in JavaScript' },
  { id: 't2', word: 'key', meaning: 'an item\'s identity' }
];

export default function Glossary() {
  return (
    <dl>
      {terms.map(term => (
        <Fragment key={term.id}>
          <dt>{term.word}</dt>
          <dd>{term.meaning}</dd>
        </Fragment>
      ))}
    </dl>
  );
}

Was React ausgibt

ReferenceError: Fragment is not defined

Warum, und die Lösung

Anders als die Kurzform <> ist <Fragment> eine Komponente, die React exportiert, also muss die Datei sie importieren. Der Lauf gibt Uncaught ReferenceError aus (Formulierung von Chrome, Firefox und Node.js; Safari formuliert es anders). Ergänzen Sie oben in main.jsx import { Fragment } from 'react';.

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

map() macht aus Daten JSX, filter() wählt vorher aus

Halten Sie die Daten in einem Array und lassen Sie JavaScript die Liste bauen: people.map(person => <li>{person.name}</li>) gibt ein <li> pro Item zurück, und {listItems} in einem <ul> zeigt sie alle. Um nur einige Items zu zeigen, filtern Sie zuerst: people.filter(person => person.role === 'Engineer') gibt ein neues, kürzeres Array zurück, das Sie dann mappen. Eine Pfeilfunktion mit einem Ausdruck nach => gibt ihn zurück. Mit einer geschweiften Klammer nach => hat sie einen Block-Body, und Sie müssen return selbst schreiben; react.dev warnt: Vergessen Sie es, wird nichts zurückgegeben.

Ein Key sagt React, welches Item welches ist

Jedes Element direkt in einem map()-Aufruf braucht einen Key: <li key={person.id}>. Der Key gehört an das äußerste Element, das der Callback zurückgibt, nicht an etwas darin. Keys lassen React Items zwischen Renderings zuordnen, wenn sie sich verschieben, eingefügt oder gelöscht werden. Nehmen Sie sie aus den Daten: IDs aus einer Datenbank, oder bei selbst erzeugten Daten einen Zähler oder crypto.randomUUID() in dem Moment, in dem Sie das Item anlegen, nie beim Rendern. Keys müssen unter Geschwistern eindeutig sein und dürfen sich nicht ändern. Braucht ein Item mehrere Elemente, packen Sie sie in <Fragment key={…}>; die Kurzform <> nimmt keinen Key.

Nicht der Index, kein Zufall, kein Prop

Ohne Key nimmt React die Position. key={index} tut dasselbe, und das geht schief, sobald Items eingefügt, gelöscht oder umsortiert werden: State wie ein Stern oder ein getippter Wert bleibt an seiner Position und landet neben einem anderen Item. key={Math.random()} ist schlimmer: Die Keys passen nie zusammen, also baut React bei jedem Rendern jedes Item neu, und Eingaben gehen verloren. Der Key ist nur ein Hinweis für React: Ihre Komponente bekommt ihn nicht, props.key ist undefined, und React warnt. Braucht die Komponente die ID, übergeben Sie sie zusätzlich als anderes Prop: <Profile key={id} userId={id} />.

Quellen

Zuletzt geprüft am 5. Oktober 2026