React Lists and Keys: Render Arrays with map()

Learn React Lists and Keys: Render Arrays with map() with practical examples, explanations, best practices, and real-world coding techniques.

Introduction to Lists and Keys in React

Many React applications display collections of data, including products, courses, notifications, comments, and search results. Instead of writing individual JSX elements for every item, React allows you to generate elements dynamically from JavaScript arrays.

The JavaScript map() method is commonly used to transform an array of data into React elements. When rendering these elements, React also requires a special attribute called key to identify each item within its collection.

Understanding lists and keys is important because keys influence how React matches elements between renders, preserves component state, and handles changes such as inserting, deleting, and reordering items.

Rendering a List with map()

The map() method creates a new array by applying a function to every element of an existing array. In React, the returned values can be JSX elements.

function TechnologyList() {
  const technologies = ['React', 'Angular', 'Vue', 'Svelte'];

  return (
    <ul>
      {technologies.map((technology) => (
        <li key={technology}>
          {technology}
        </li>
      ))}
    </ul>
  );
}

export default TechnologyList;

React evaluates the map() expression and renders one list item for every technology. Each item receives a key based on its value. This is suitable here because the technology names are unique and stable within the list.

For arrays containing duplicate values, use a different stable identifier instead of the value itself.

Rendering Arrays of Objects

Real applications commonly receive arrays of objects from APIs or databases. Each object can contain multiple properties, such as an ID, name, description, or price.

const courses = [
  { id: 101, title: 'React Fundamentals', level: 'Beginner' },
  { id: 102, title: 'TypeScript Essentials', level: 'Intermediate' },
  { id: 103, title: 'Advanced JavaScript', level: 'Advanced' }
];

function CourseList() {
  return (
    <div>
      {courses.map((course) => (
        <article key={course.id}>
          <h3>{course.title}</h3>
          <p>Level: {course.level}</p>
        </article>
      ))}
    </div>
  );
}

export default CourseList;

The course.id property is used as the key because it uniquely identifies each course. Database-generated IDs are generally suitable when they remain unchanged throughout the item's lifetime.

What Are Keys in React?

A key is a special React attribute that identifies an element among its siblings. React uses keys while reconciling the previous render with the next render.

When items are added, removed, or reordered, stable keys help React associate the correct component instances with the corresponding data. This is especially important when list items contain local state, inputs, or other interactive elements.

const users = [
  { id: 'u1', name: 'Asha' },
  { id: 'u2', name: 'Rahul' },
  { id: 'u3', name: 'Meera' }
];

function UserList() {
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>
          {user.name}
        </li>
      ))}
    </ul>
  );
}

Keys must be unique among siblings in the same list. They do not need to be globally unique across the entire application, so separate lists may reuse the same key values.

Why Stable Keys Matter

Imagine a list of editable tasks. If a new task is inserted at the beginning, the positions of existing tasks change. When React uses stable IDs, it can continue associating each task with the correct component instance.

If array positions are used as keys, React may instead associate existing component state with a different task after the insertion or reordering.

Key strategy Behavior Recommendation
Stable database ID Remains associated with the same item. Preferred for database records.
Stable unique string Identifies the same item across renders. Suitable for fixed or locally created data.
Array index Can associate state with the wrong item when order changes. Use only for truly static lists without stable IDs.
Random value during render Changes identity between renders and can repeatedly remount components. Avoid.

Why Array Indexes Are Often Poor Keys

The map() callback provides an index, but that index represents the item's current position rather than its identity.

Not recommended for changing lists

{tasks.map((task, index) => (
  <TaskItem key={index} task={task} />
))}

Recommended

{tasks.map((task) => (
  <TaskItem key={task.id} task={task} />
))}

Index keys are especially problematic when items can be inserted, removed, sorted, or filtered. They may cause input values, focus, or component state to appear associated with the wrong item.

An index can be acceptable for a fixed list that never changes order, never gains or loses entries, and has no stable identifier. Even then, a meaningful stable key is preferable when available.

Filtering Lists Before Rendering

The filter() method creates a new array containing only elements that satisfy a condition. It can be combined with map() to display a subset of data.

const courses = [
  { id: 1, title: 'HTML Basics', published: true },
  { id: 2, title: 'React Hooks', published: false },
  { id: 3, title: 'JavaScript Fundamentals', published: true }
];

function PublishedCourses() {
  const publishedCourses = courses.filter(
    (course) => course.published
  );

  return (
    <ul>
      {publishedCourses.map((course) => (
        <li key={course.id}>{course.title}</li>
      ))}
    </ul>
  );
}

Filtering does not modify the original array. Each filtered item retains its original ID, so the same key remains valid even when the displayed subset changes.

You can also combine filtering and mapping in a single expression, but separating them into named variables is often easier to read when the logic becomes more complex.

Rendering an Empty List

An empty array produces no elements when mapped. In most applications, it is helpful to display a clear message rather than leaving an unexplained blank area.

function SearchResults({ results }) {
  if (results.length === 0) {
    return <p>No matching results found.</p>;
  }

  return (
    <ul>
      {results.map((result) => (
        <li key={result.id}>
          {result.title}
        </li>
      ))}
    </ul>
  );
}

This example assumes results is an array. For asynchronous data, distinguish the loading state from a successfully loaded empty result. Otherwise, users might see a misleading empty-state message while the request is still running.

For more conditional UI patterns, read React Conditional Rendering.

Rendering Lists with Reusable Components

When list items contain several elements or interactive behavior, extracting them into reusable components keeps the parent component focused on managing the collection.

function CourseCard({ course }) {
  return (
    <article>
      <h3>{course.title}</h3>
      <p>{course.description}</p>
    </article>
  );
}

function CourseGrid({ courses }) {
  return (
    <div className="course-grid">
      {courses.map((course) => (
        <CourseCard
          key={course.id}
          course={course}
        />
      ))}
    </div>
  );
}

The key belongs on the CourseCard element created inside map(), not on the root article inside CourseCard.

React uses the key internally when matching siblings. The child component does not receive key as an ordinary prop. If the component needs its ID, pass it separately, such as courseId={course.id}.

Learn more about component composition in React Components and passing data in React Props.

Using Fragments in Lists

Sometimes one array item needs to render multiple sibling elements without introducing an additional HTML wrapper. React Fragments can group those elements.

The shorthand Fragment syntax cannot receive a key. When rendering multiple elements from a list, use the explicit Fragment component.

import { Fragment } from 'react';

function DefinitionList({ terms }) {
  return (
    <dl>
      {terms.map((term) => (
        <Fragment key={term.id}>
          <dt>{term.name}</dt>
          <dd>{term.description}</dd>
        </Fragment>
      ))}
    </dl>
  );
}

export default DefinitionList;

The Fragment provides a stable identity for the group without creating an extra DOM element. This is useful when additional wrappers would interfere with semantic HTML or layout requirements.

Updating Lists with React State

Lists frequently change in response to user interactions. React state can store the collection, while immutable array operations create the updated values.

Avoid directly mutating a state array using operations such as push(), splice(), or sort(). Instead, create a new array and pass it to the state setter.

Adding an item

setTasks((currentTasks) => [
  ...currentTasks,
  { id: crypto.randomUUID(), title: 'New task' }
]);

Removing an item

setTasks((currentTasks) =>
  currentTasks.filter((task) => task.id !== taskId)
);

Updating an item

setTasks((currentTasks) =>
  currentTasks.map((task) =>
    task.id === taskId
      ? { ...task, completed: true }
      : task
  )
);

Each operation returns a new array without changing the previous state. The examples also preserve existing item IDs, allowing React to maintain stable identities across updates.

The crypto.randomUUID() example generates an ID when the new item is created, not during rendering. It requires a supported environment and a secure context. A server-provided ID or another stable ID generator is also appropriate.

See React State for more information about immutable updates.

Sorting Lists Without Mutating State

JavaScript's sort() method modifies the array on which it is called. If the array comes from React state or props, create a copy before sorting.

function SortedCourses({ courses }) {
  const sortedCourses = [...courses].sort((a, b) =>
    a.title.localeCompare(b.title)
  );

  return (
    <ul>
      {sortedCourses.map((course) => (
        <li key={course.id}>{course.title}</li>
      ))}
    </ul>
  );
}

The spread operator creates a new array before sorting, leaving the original array unchanged. Stable IDs remain associated with the same courses even when their positions change.

For expensive filtering or sorting of large collections, useMemo can sometimes help avoid unnecessary recalculation when dependencies have not changed. Measure the cost before introducing memoization.

Explore React useMemo and React Performance for optimization techniques.

Keys and Component State Preservation

React associates component state with its position and identity in the rendered tree. Keys help determine that identity when siblings change.

Consider a list of task components where each task has an editable input. If the tasks are reordered, stable keys help the input state remain associated with the intended task.

function TaskList({ tasks }) {
  return (
    <div>
      {tasks.map((task) => (
        <TaskEditor
          key={task.id}
          task={task}
        />
      ))}
    </div>
  );
}

Conversely, intentionally changing a component's key tells React to treat it as a different component and reset its local state. This can be useful for resetting a form when switching between records, but should be done deliberately.

function ProfileEditor({ selectedUser }) {
  return (
    <EditProfileForm
      key={selectedUser.id}
      user={selectedUser}
    />
  );
}

When selectedUser.id changes, React creates a fresh instance of the form instead of preserving the previous user's local form state.

Common Mistakes with Lists and Keys

Missing the key attribute

Elements created by map() should have stable keys. Missing keys can produce development warnings and make identity tracking less reliable.

Using duplicate keys

Duplicate keys among siblings can cause incorrect matching and unsupported rendering behavior. Use an identifier that is unique within the collection.

Generating keys during rendering

Avoid key={Math.random()} or key={crypto.randomUUID()} inside a render loop. Changing keys on every render forces React to recreate the corresponding components and can discard local state.

Using array indexes for dynamic data

Index keys can associate state with the wrong item when the collection is reordered, filtered, or modified. Prefer stable IDs.

Mutating state arrays

Do not directly change arrays stored in state. Use operations that produce new arrays and new objects for modified items.

Putting the key in the wrong component

Place the key on the element returned by the list mapping operation, even when that element is a custom component.

Treating keys as normal props

The key attribute is reserved for React and is not passed to the component as a normal prop. Pass a separate ID prop when the child needs it.

Best Practices for React Lists

  • Use map() to transform arrays into JSX elements.
  • Choose stable, unique keys from the underlying data whenever possible.
  • Keep keys consistent when items are filtered, sorted, inserted, or removed.
  • Use filter() and map() to create updated arrays without mutating state.
  • Provide meaningful empty-state messages when a collection contains no items.
  • Extract complex list items into reusable components.
  • Use explicit keyed Fragments when one item needs multiple sibling elements.
  • Generate identifiers when data is created, not while rendering it.
  • Use semantic HTML such as ul, ol, and li for actual lists.
  • Optimize expensive list operations only when performance measurements justify it.

Key Takeaway

React lists transform JavaScript arrays into reusable UI elements, while keys provide stable identities for those elements. Using unique IDs, immutable array operations, and appropriate empty-state handling makes dynamic collections easier to maintain and helps React preserve the correct component state when data changes.

Code Examples

function TechnologyList() {
  const technologies = ["React", "Angular", "Vue", "Svelte"];

  return (
    <ul>
      {technologies.map((technology) => (
        <li key={technology}>{technology}</li>
      ))}
    </ul>
  );
}

export default TechnologyList;
    
const courses = [
  { id: 101, title: "React Fundamentals", level: "Beginner" },
  { id: 102, title: "TypeScript Essentials", level: "Intermediate" },
  { id: 103, title: "Advanced JavaScript", level: "Advanced" }
];

function CourseList() {
  return (
    <ul>
      {courses.map((course) => (
        <li key={course.id}>
          <strong>{course.title}</strong> - {course.level}
        </li>
      ))}
    </ul>
  );
}

export default CourseList;
    
function PublishedCourses({ courses }) {
  const publishedCourses = courses.filter(
    (course) => course.published
  );

  if (publishedCourses.length === 0) {
    return <p>No published courses available.</p>;
  }

  return (
    <ul>
      {publishedCourses.map((course) => (
        <li key={course.id}>{course.title}</li>
      ))}
    </ul>
  );
}

export default PublishedCourses;
    
import { Fragment } from "react";

function DefinitionList({ terms }) {
  return (
    <dl>
      {terms.map((term) => (
        <Fragment key={term.id}>
          <dt>{term.name}</dt>
          <dd>{term.description}</dd>
        </Fragment>
      ))}
    </dl>
  );
}

export default DefinitionList;
    
import { useState } from "react";

function TaskManager() {
  const [tasks, setTasks] = useState([
    { id: "task-1", title: "Learn React" },
    { id: "task-2", title: "Practice JSX" }
  ]);

  function addTask() {
    const newTask = {
      id: crypto.randomUUID(),
      title: "New task"
    };

    setTasks((currentTasks) => [...currentTasks, newTask]);
  }

  function removeTask(taskId) {
    setTasks((currentTasks) =>
      currentTasks.filter((task) => task.id !== taskId)
    );
  }

  return (
    <section>
      <button type="button" onClick={addTask}>Add task</button>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>
            {task.title}
            <button
              type="button"
              onClick={() => removeTask(task.id)}
              aria-label={"Remove " + task.title}
            >
              Remove
            </button>
          </li>
        ))}
      </ul>
    </section>
  );
}

export default TaskManager;
    
function SortedCourses({ courses }) {
  const sortedCourses = [...courses].sort((a, b) =>
    a.title.localeCompare(b.title)
  );

  return (
    <ul>
      {sortedCourses.map((course) => (
        <li key={course.id}>{course.title}</li>
      ))}
    </ul>
  );
}

export default SortedCourses;
    

Pro Tip

Use stable IDs from your data as React keys. Avoid array indexes for collections that can change order, and never generate random keys during rendering. Keys control component identity, so changing a key can also intentionally reset component state.

🎨 IgKnight Docs

Master HTML, CSS, JS, Python, .NET and more with easy-to-follow lessons and practical code examples.

© 2025 IgKnight Learning Platform. All rights reserved.