Skip to content
Laviyx Learn

Try

React course menu

Rendering Lists in React with map() and Keys

Display arrays in React: turning data into elements with map(), why every list item needs a stable key, choosing keys, filtering and sorting before rendering, rendering tables, and the problem with array indexes as keys.

Beginner11 min readUpdated
On this page

Most interfaces show lists: products, messages, search results, table rows. In React you keep the data in an array and turn each item into JSX with the array's map() method.

map() turns data into elements#

JSX
const fruits = ["Mango", "Banana", "Guava"];

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

map returns a new array of <li> elements, and React renders an array of elements one after another.

Keys#

Each item in a list needs a key prop: a value that is unique among its siblings and stays the same for that item. Keys let React match items between renders, so when the list changes (an item is added, removed or reordered) it updates the right elements and keeps each item's state with the right item. Without keys, React warns in the console: Each child in a list should have a unique "key" prop.

Real data usually has an id, which makes the best key:

JSX
const students = [
  { id: 101, name: "Aarav", marks: 88 },
  { id: 102, name: "Diya", marks: 94 },
  { id: 103, name: "Kabir", marks: 71 },
];

export default function App() {
  return (
    <table style={{ borderCollapse: "collapse" }}>
      <thead>
        <tr><th align="left">Roll no.</th><th align="left">Name</th><th align="right">Marks</th></tr>
      </thead>
      <tbody>
        {students.map((s) => (
          <tr key={s.id}>
            <td>{s.id}</td>
            <td style={{ padding: "0 16px" }}>{s.name}</td>
            <td align="right">{s.marks}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}
Result

Rules for keys:

  • Use ids from your data (database ids, product codes). For data you create, generate an id when the item is created (for example with crypto.randomUUID()).
  • Do not generate keys during rendering (key={Math.random()}): a new key every render makes React recreate every item.
  • The key goes on the outermost element returned from map, and is not passed to the component as a prop.

Why the index is a risky key#

map((item, index) => <li key={index}>) silences the warning, but if items are inserted, removed or reordered, the index of an item changes, and React attaches the wrong state to the wrong row. Try it: type something in the boxes, then add an item at the top.

JSX
import { useState } from "react";

export default function App() {
  const [tasks, setTasks] = useState([
    { id: 1, text: "Buy milk" },
    { id: 2, text: "Call plumber" },
  ]);
  const addToTop = () => setTasks([{ id: Date.now(), text: "New task" }, ...tasks]);

  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: 24 }}>
      <div>
        <strong>key = index</strong>
        {tasks.map((task, index) => (
          <p key={index}>{task.text} <input placeholder="note" size={8} /></p>
        ))}
      </div>
      <div>
        <strong>key = id</strong>
        {tasks.map((task) => (
          <p key={task.id}>{task.text} <input placeholder="note" size={8} /></p>
        ))}
      </div>
      <button onClick={addToTop} style={{ alignSelf: "start" }}>Add task at top</button>
    </div>
  );
}
Result

With index keys, the notes you typed stay in the first rows while the task names shift down, so they end up next to the wrong tasks. With id keys, each note moves with its task. The index is only safe for lists that never change order and never have items inserted or removed.

Filter and sort before rendering#

Prepare the array first with filter, sort or slice, then map it:

JSX
const products = [
  { id: "p1", name: "Headphones", price: 1999, inStock: true },
  { id: "p2", name: "Keyboard", price: 1299, inStock: false },
  { id: "p3", name: "Mouse", price: 599, inStock: true },
  { id: "p4", name: "Webcam", price: 2499, inStock: true },
];

export default function App() {
  const available = products
    .filter((p) => p.inStock)
    .toSorted((a, b) => a.price - b.price);

  return (
    <div>
      <p>{available.length} of {products.length} products in stock, cheapest first:</p>
      <ol>
        {available.map((p) => (
          <li key={p.id}>{p.name} — ₹{p.price}</li>
        ))}
      </ol>
    </div>
  );
}
Result

toSorted returns a new sorted array. The older sort sorts the array in place, which would change the original data during rendering; avoid that (or copy first with [...products].sort(...)).

Rendering components from a list#

JSX
function Contact({ name, phone }) {
  return (
    <div style={{ borderBottom: "1px solid #eee", padding: "6px 0" }}>
      <strong>{name}</strong><br />
      <small>{phone}</small>
    </div>
  );
}

const contacts = [
  { id: "c1", name: "Mom", phone: "98450 12345" },
  { id: "c2", name: "Office", phone: "080 4123 4567" },
];

export default function App() {
  return (
    <div>
      {contacts.map((c) => (
        <Contact key={c.id} name={c.name} phone={c.phone} />
      ))}
    </div>
  );
}
Result

Empty lists#

Always handle the empty case, so users see a message rather than a blank space:

JSX
{items.length === 0 ? <p>No results found.</p> : <ul>{items.map(...)}</ul>}

Common mistakes#

Practice#

Practice exercise

Given the movies below, show only those with a rating of 8 or more, highest rating first, as a list "Title (year) ⭐ rating". Show "No movies" if none match.

JavaScript
const movies = [
  { id: 1, title: "Lagaan", year: 2001, rating: 8.1 },
  { id: 2, title: "Dhoom", year: 2004, rating: 6.7 },
  { id: 3, title: "3 Idiots", year: 2009, rating: 8.4 },
  { id: 4, title: "Taare Zameen Par", year: 2007, rating: 8.3 },
];
Show hint

movies.filter((m) => m.rating >= 8).toSorted((a, b) => b.rating - a.rating), then map to <li key={m.id}>.

Show solution
JSX
const movies = [
  { id: 1, title: "Lagaan", year: 2001, rating: 8.1 },
  { id: 2, title: "Dhoom", year: 2004, rating: 6.7 },
  { id: 3, title: "3 Idiots", year: 2009, rating: 8.4 },
  { id: 4, title: "Taare Zameen Par", year: 2007, rating: 8.3 },
];

export default function App() {
  const top = movies.filter((m) => m.rating >= 8).toSorted((a, b) => b.rating - a.rating);
  if (top.length === 0) return <p>No movies</p>;
  return (
    <ul>
      {top.map((m) => (
        <li key={m.id}>{m.title} ({m.year}) ⭐ {m.rating}</li>
      ))}
    </ul>
  );
}
Result

Key takeaway#

Next: Handling Events.

Finished this lesson?