Skip to main content

Render Lists with Filter, Sort, and Map in React

Rendering lists in React requires transforming data before displaying it. By combining .filter(), .sort(), and .map(), you can selectively render subsets of data in any order. This article shows how to chain these array methods safely without mutating the original data, a critical requirement in React's state-driven architecture.

Key Takeaways

  • .filter() creates a new array with only items that pass a test function; it does not mutate the original
  • .sort() mutates the array in place, so always create a copy with the spread operator [...array] before sorting
  • Chaining .filter().sort().map() is the idiomatic pattern for transforming data before rendering
  • Always use key props based on unique, stable identifiers when rendering filtered or sorted lists
  • Never mutate arrays held in React state; create new arrays instead

Prerequisites

Before reading this article, you should understand:

  • How .map() transforms an array of data into an array of React components
  • JavaScript array method syntax and arrow functions
  • The purpose and behavior of .filter() and .sort() in vanilla JavaScript

Preparing Data Before Rendering

The core principle is simple: you control the data before you render it. Your render logic can accept any array, so the transformation pipeline is:

  1. Start with an array of raw data
  2. Filter it to keep only items matching a condition
  3. Sort it to arrange items in the desired order
  4. Map it to transform each item into a React component

Each step produces a new array, preserving the original data.

Filtering Lists with .filter()

The .filter() method creates a new array containing only items where the test function returns true.

import React from 'react';

function ScientistList() {
const people = [
{ id: 1, name: 'Marie Curie', profession: 'physicist' },
{ id: 2, name: 'Dmitri Mendeleev', profession: 'chemist' },
{ id: 3, name: 'Dorothy Hodgkin', profession: 'chemist' }
];

// Step 1: Filter for chemists only
const chemists = people.filter(person =>
person.profession === 'chemist'
);

// Step 2: Map over the filtered array
const listItems = chemists.map(person =>
<li key={person.id}>
<b>{person.name}</b> ({person.profession})
</li>
);

return (
<>
<h1>Chemists</h1>
<ul>{listItems}</ul>
</>
);
}

export default ScientistList;

How this works:

  • people.filter(person => person.profession === 'chemist') evaluates the arrow function for each item
  • Items where the function returns true are kept; items where it returns false are removed
  • The result is a new array (chemists) containing only Dmitri Mendeleev and Dorothy Hodgkin
  • Mapping over this smaller array is more efficient than mapping and conditionally rendering

The original people array is unchanged, preserving immutability.

Sorting Lists Safely

The .sort() method has a critical gotcha: it mutates the original array. In React, mutating state leads to bugs. Always create a copy first.

import React from 'react';

function ScientistList() {
const people = [
{ id: 1, name: 'Marie Curie', profession: 'physicist' },
{ id: 2, name: 'Dmitri Mendeleev', profession: 'chemist' },
{ id: 3, name: 'Dorothy Hodgkin', profession: 'chemist' }
];

// IMPORTANT: Create a shallow copy before sorting
const sortedPeople = [...people].sort((a, b) => {
return a.name.localeCompare(b.name);
});

const listItems = sortedPeople.map(person =>
<li key={person.id}>{person.name}</li>
);

return (
<>
<h1>Scientists (Sorted by Name)</h1>
<ul>{listItems}</ul>
</>
);
}

export default ScientistList;

Why the spread operator [...people] is essential:

  • .sort() modifies the array it's called on directly (mutation)
  • If people is part of your component state, mutating it bypasses React's detection of changes
  • The spread operator [...] creates a shallow copy; .sort() then mutates only the copy, not the original
  • a.name.localeCompare(b.name) compares strings alphabetically and returns < 0, 0, or > 0 for sorting

Chaining Methods for Complex Transformations

Combining .filter(), .sort(), and .map() in a single chain is the idiomatic React pattern. Each method returns an array, allowing the next method to process it.

import React from 'react';

function ScientistList() {
const people = [
{ id: 1, name: 'Marie Curie', profession: 'physicist', decade: 1890 },
{ id: 2, name: 'Dmitri Mendeleev', profession: 'chemist', decade: 1860 },
{ id: 3, name: 'Dorothy Hodgkin', profession: 'chemist', decade: 1930 }
];

const listItems = people
.filter(person => person.profession === 'chemist')
.sort((a, b) => b.decade - a.decade) // Newest first
.map(person =>
<li key={person.id}>
<b>{person.name}</b> ({person.decade}s)
</li>
);

return (
<>
<h1>Chemists (Newest Era First)</h1>
<ul>{listItems}</ul>
</>
);
}

export default ScientistList;

The execution order:

  1. .filter(...) produces [Mendeleev, Hodgkin] (only chemists)
  2. .sort(...) reorders to [Hodgkin, Mendeleev] (1930s before 1860s)
  3. .map(...) transforms each into an <li> element

This pipeline is clear, concise, and declarative: you immediately see what transformations happen before rendering.

Handling Complex Filter Conditions

Multiple filters can be chained or combined in a single condition:

// Filter for chemists from the 1900s onward
const modernChemists = people
.filter(person =>
person.profession === 'chemist' && person.decade >= 1900
)
.sort((a, b) => a.name.localeCompare(b.name))
.map(person => <li key={person.id}>{person.name}</li>);

Or apply filters sequentially for clarity:

const chemists = people.filter(person => person.profession === 'chemist');
const modern = chemists.filter(person => person.decade >= 1900);
const sorted = [...modern].sort((a, b) => a.name.localeCompare(b.name));
const listItems = sorted.map(person => <li key={person.id}>{person.name}</li>);

Choose the style that matches your team's readability preferences.

Why Keys Matter When Filtering and Sorting

When you filter or sort a list, the items change position or disappear. React uses the key prop to track which item is which across renders. Without stable keys, React may reuse DOM nodes incorrectly.

// Bad: Using array index as key (breaks when list is filtered/sorted)
list.map((item, index) => <li key={index}>{item.name}</li>)

// Good: Using a unique, stable identifier
list.map(item => <li key={item.id}>{item.name}</li>)

When you filter [A, B, C] down to [A, C], a key-based on index 0, 1 would be reused incorrectly. A stable id ensures each item's state (like an <input> value) is preserved correctly.

Frequently Asked Questions

What if my data doesn't have a unique id field?

Use a generating function to create stable IDs once when data is fetched, not during render. If you must derive a key, use JSON.stringify() on the object's unique properties, but this is slower. Ideally, get an ID from the source (API, database).

Can I use .map() directly on state without filter or sort?

Yes. You only need to filter or sort if you want a subset or different ordering. However, always derive the transformed array outside your JSX if possible, or use useMemo() to avoid recomputing on every render.

What's the performance difference between filtering inline and pre-filtering?

Filtering inline (e.g., items.filter(...).map(...) in JSX) is re-evaluated on every render. For large lists, extract the filtered array into a variable or use useMemo() to compute it once per dependency change, improving performance.

How do I filter and map in one step without a separate variable?

Use the chaining syntax shown above. The trade-off is readability: single-line chains are compact but harder to debug. Multi-line chains with intermediate variables are easier to step through in a debugger.

Can I combine filter and map without sorting?

Absolutely. Sorting is optional. Use .filter().map() if you don't need to reorder:

const activeUsers = users.filter(u => u.isActive).map(u => <li key={u.id}>{u.name}</li>);

Further Reading