Skip to content

React Tutorial Props and Data

Filtering and Sorting Lists in React

Lesson 42 of 156 1 min read By Deepak Paswan Updated Oct 10, 2026

Runnable lesson · press Try it on the examples to edit and run them here.

Users expect to search, filter and sort. In React this takes three steps: keep the raw data and the user’s choices in state, derive the visible list from them during rendering, then map it.

The recipe

App.jsx
JSX
const PRODUCTS = [
  { id: 1, name: "Keyboard", price: 49, inStock: true },
  { id: 2, name: "Mouse", price: 25, inStock: false },
  { id: 3, name: "Monitor", price: 189, inStock: true },
  { id: 4, name: "Cable", price: 9, inStock: false },
];

function App() {
  const [onlyInStock, setOnlyInStock] = React.useState(false);
  const [sortByPrice, setSortByPrice] = React.useState(false);

  // Derive the visible list. This is not state: it is calculated from state on each render.
  let visible = onlyInStock ? PRODUCTS.filter((p) => p.inStock) : PRODUCTS;
  if (sortByPrice) {
    visible = [...visible].sort((a, b) => a.price - b.price); // copy first!
  }

  return (
    <div>
      <button onClick={() => setOnlyInStock(!onlyInStock)}>Only in stock</button>{" "}
      <button onClick={() => setSortByPrice(!sortByPrice)}>Sort by price</button>
      <p>Showing {visible.length} of {PRODUCTS.length}</p>
      <ul>
        {visible.map((p) => (
          <li key={p.id}>{p.name} - ${p.price}</li>
        ))}
      </ul>
    </div>
  );
}

Notice what is stored in state: only the two booleans. The filtered and sorted list is derived. It cannot get out of sync with the choices, because it is recalculated every time. Storing visibleProducts in state as well would be a bug waiting to happen.

Sort a copy, never the original

Array.prototype.sort changes the array it is called on. If that array is state or a prop, you have just mutated it. Make a copy first:

script.js
JS
[...items].sort(compare)       // spread copy
items.slice().sort(compare)    // slice copy
items.toSorted(compare)        // built-in non-mutating version in modern browsers

toSorted, toReversed and toSpliced exist in current browsers; check feature support for the browsers you target.

Sorting strings and dates

App.jsx
JSX
function App() {
  const names = ["Linus", "ada", "Grace"];
  const [desc, setDesc] = React.useState(false);

  const sorted = [...names].sort((a, b) => a.localeCompare(b, "en", { sensitivity: "base" }));
  if (desc) sorted.reverse();

  return (
    <div>
      <button onClick={() => setDesc(!desc)}>Z to A</button>
      <ol>{sorted.map((n) => <li key={n}>{n}</li>)}</ol>
    </div>
  );
}

localeCompare sorts text correctly for people (case and accents), unlike the default sort, which compares character codes. For dates, compare the timestamps (new Date(a) - new Date(b)), or ISO date strings, which sort correctly as text.

Search and filter together

App.jsx
JSX
function App() {
  const all = ["Orange", "Apple", "Mango", "Pear", "Grape"];
  const [query, setQuery] = React.useState("");

  const visible = all.filter((f) => f.toLowerCase().includes(query.trim().toLowerCase()));

  return (
    <div>
      <input aria-label="Search" value={query} onChange={(e) => setQuery(e.target.value)} />
      <p>Showing {visible.length}</p>
      <ul>{visible.map((f) => <li key={f}>{f}</li>)}</ul>
    </div>
  );
}

Large lists

Filtering a few hundred items on every key press is instant. If you have thousands of items and typing feels slow, options are useMemo (see useMemo), useDeferredValue, paging (see Pagination Basics) or virtualising the list. Measure first; do not optimise a list that is already fast.

Key points

  • Keep raw data and the user’s choices in state; derive the visible list while rendering.
  • Always sort a copy, never state or props directly.
  • Handle the “no matches” case; see Empty States.