CodeMIRAI
0 XP

Lists and keys

3 min read 6 runnable examples Quiz included

Rendering an array with map

React (JSX)
function App() {
  const courses = ["Python", "JavaScript", "SQL", "React"];
  return (
    <ul>
      {courses.map((course) => (
        <li key={course}>{course}</li>
      ))}
    </ul>
  );
}

map turns each item of the array into a piece of JSX, and React renders the resulting array.

A list of objects

React (JSX)
function App() {
  const lessons = [
    { id: 101, title: "Variables", minutes: 8, done: true },
    { id: 102, title: "Loops", minutes: 12, done: true },
    { id: 103, title: "Functions", minutes: 15, done: false },
  ];
  const total = lessons.reduce((sum, l) => sum + l.minutes, 0);

  return (
    <div>
      {lessons.map((lesson) => (
        <div key={lesson.id} style={{ padding: 8, borderBottom: "1px solid #eee" }}>
          {lesson.done ? "✔" : "○"} <strong>{lesson.title}</strong> ({lesson.minutes} min)
        </div>
      ))}
      <p>Total: {total} minutes</p>
    </div>
  );
}

Keys

React (JSX)
function App() {
  const fruits = ["Mango", "Apple", "Guava"];
  return (
    <ul>
      {fruits.map((fruit) => <li>{fruit}</li>)}
    </ul>
  );
}

The list displays fine, but the console shows React's warning: each child in a list should have a unique key prop. React uses keys to tell items apart between renders, so that when the list changes, it updates the right elements instead of rebuilding them all.

  • A key must be unique among its siblings.
  • It must be stable: the same item always gets the same key.
  • The best key is an id from your data, like lesson.id.

Filtering and sorting before rendering

React (JSX)
import { useState } from "react";

const PEOPLE = [
  { id: 1, name: "Asha", score: 92 },
  { id: 2, name: "Leo", score: 78 },
  { id: 3, name: "Mei", score: 85 },
  { id: 4, name: "Raj", score: 88 },
  { id: 5, name: "Zoe", score: 95 },
];

function App() {
  const [query, setQuery] = useState("");
  const [best, setBest] = useState(false);

  const shown = PEOPLE
    .filter((p) => p.name.toLowerCase().includes(query.toLowerCase()))
    .sort((a, b) => (best ? b.score - a.score : a.name.localeCompare(b.name)));

  return (
    <div>
      <input placeholder="Search names" value={query} onChange={(e) => setQuery(e.target.value)} />{" "}
      <label><input type="checkbox" checked={best} onChange={(e) => setBest(e.target.checked)} /> highest score first</label>
      <ol>{shown.map((p) => <li key={p.id}>{p.name}: {p.score}</li>)}</ol>
      {shown.length === 0 && <p>No matches.</p>}
    </div>
  );
}

The filtered, sorted list is calculated on every render from the data and the controls. There's no need to store it in state: anything you can calculate, calculate.

Adding and removing items

React (JSX)
import { useState } from "react";

let nextId = 3;

function App() {
  const [todos, setTodos] = useState([
    { id: 1, text: "Learn map", done: true },
    { id: 2, text: "Understand keys", done: false },
  ]);
  const [text, setText] = useState("");

  function add(e) {
    e.preventDefault();
    if (!text.trim()) return;
    setTodos([...todos, { id: nextId++, text: text.trim(), done: false }]);
    setText("");
  }

  const toggle = (id) => setTodos(todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t)));
  const remove = (id) => setTodos(todos.filter((t) => t.id !== id));

  return (
    <div>
      <form onSubmit={add}>
        <input value={text} onChange={(e) => setText(e.target.value)} placeholder="New task" />{" "}
        <button>Add</button>
      </form>
      <ul style={{ listStyle: "none", padding: 0 }}>
        {todos.map((t) => (
          <li key={t.id} style={{ margin: "6px 0" }}>
            <input type="checkbox" checked={t.done} onChange={() => toggle(t.id)} />{" "}
            <span style={{ textDecoration: t.done ? "line-through" : "none" }}>{t.text}</span>{" "}
            <button onClick={() => remove(t.id)}>✕</button>
          </li>
        ))}
      </ul>
      <p>{todos.filter((t) => !t.done).length} left to do</p>
    </div>
  );
}

Never change a state array in place. Make a new one instead: [...todos, item] adds, filter removes, and map replaces one item.

Why not use the index as the key?

Type a note next to Asha in both lists, then press the button:

React (JSX)
import { useState } from "react";

let nextId = 3;

function People({ people, keyByIndex }) {
  return (
    <ul>
      {people.map((person, index) => (
        <li key={keyByIndex ? index : person.id}>
          {person.name}: <input placeholder="note" size={8} />
        </li>
      ))}
    </ul>
  );
}

function App() {
  const [people, setPeople] = useState([{ id: 1, name: "Asha" }, { id: 2, name: "Leo" }]);
  function addAtTop() {
    const id = nextId++;
    setPeople([{ id, name: "Person " + id }, ...people]);
  }
  return (
    <div>
      <button onClick={addAtTop}>Add a person at the top</button>
      <div style={{ display: "flex", gap: 32 }}>
        <div><strong>key = index</strong><People people={people} keyByIndex /></div>
        <div><strong>key = id</strong><People people={people} /></div>
      </div>
    </div>
  );
}

On the left, your note jumps to the new person. Before the change, Asha's row had key 0; afterwards key 0 belongs to the new person, so React reuses Asha's old input, note and all, for the wrong row. On the right, each row keeps its id, and the note stays with Asha. An index is only a safe key for a list that never changes order and never gains or loses items in the middle.

Finished this lesson?Mark it complete to earn +20 XP and move your progress on.