React Sorting Lists

In the last lesson you learned React Filtering Lists, so you can already show only the items the user wants. Now let’s put that list in order with .sort(), and dodge the one sharp edge that bites almost everyone in React.

🤔 Why sort a list at all?

Real apps almost never show a list in some random order. People want it arranged in a way that makes sense for them.

  • A shopping page lets you sort products by price, low to high or high to low.
  • A messages screen shows the newest message first.
  • A list of names reads better from A to Z, like a phone contact list.

So sorting is just arranging the same items in a chosen order. The items don’t change. Only their position does.

🧩 The compare function

.sort() doesn’t magically know what “in order” means for you. You tell it, with a small function called the compare function.

  • The compare function gets two items at a time, usually named a and b.
  • You return a negative number if a should come first, a positive number if b should come first, and zero if they’re equal.
  • For numbers, the easy trick is a - b. That gives a negative, positive, or zero all on its own.

Here we sort plain numbers from smallest to largest using a compare function.

const numbers = [30, 4, 21, 100];
const sorted = numbers.sort((a, b) => a - b);
// [4, 21, 30, 100]

So a - b reads as “smaller numbers first”. Flip it to b - a and you get largest first instead.

Plain sort messes up numbers

If you call .sort() with no compare function, it sorts everything as text. So [30, 4, 21, 100] becomes [100, 21, 30, 4], because “100” comes before “21” as text. Always pass (a, b) => a - b when you sort numbers.

🔤 Sorting text with localeCompare

Numbers use subtraction, but you can’t subtract two words. For strings, JavaScript gives you a method made for this job.

  • localeCompare compares two strings and returns that same negative, positive, or zero.
  • It also handles capital letters and accented letters in a sensible order.
  • So a.localeCompare(b) sorts A to Z, and b.localeCompare(a) sorts Z to A.

Here we put a list of names in alphabetical order using localeCompare.

const names = ["Riya", "Arjun", "Alex", "Meera"];
const sorted = names.sort((a, b) => a.localeCompare(b));
// ["Alex", "Arjun", "Meera", "Riya"]

So each name is compared with the next, and localeCompare decides which one comes first.

⚠️ .sort() mutates the original array

Here is the sharp edge. This is the one thing to remember from the whole lesson. .sort() does not return a fresh sorted copy and leave your list alone. It changes the original array in place and then returns that same array.

  • In plain JavaScript that’s usually fine, you don’t notice.
  • But React state must never be changed in place. React checks if the array is a new array to decide whether to re-render.
  • So sorting state directly changes the data behind React’s back, and the screen can show stale or wrong data.

This pair shows the trap next to the fix.

const [items, setItems] = useState([3, 1, 2]);
// ❌ Wrong - sorts the SAME array that is in state, mutating it
items.sort((a, b) => a - b);
// ✅ Right - copy first with spread, then sort the copy
const sorted = [...items].sort((a, b) => a - b);

So [...items] makes a brand-new array first. You sort that copy, and your state array is never touched.

Always copy before you sort state

Never call .sort() straight on a state array or a prop. Spread it into a new array first: [...items].sort(...). The same goes for .reverse(), it also changes the array in place.

🗂️ Sorting an array of objects by a field

Real lists are usually objects like products with a name and a price, so you sort them by reaching into the field you care about:

  • For a number field like price, use a.price - b.price.
  • For a text field like name, use a.name.localeCompare(b.name).
  • And remember to copy the array first, just like always.

Here we sort a list of product objects by their price, cheapest first.

const products = [
{ id: 1, name: "Pen", price: 30 },
{ id: 2, name: "Notebook", price: 12 },
{ id: 3, name: "Eraser", price: 5 },
];
const byPrice = [...products].sort((a, b) => a.price - b.price);
// Eraser (5), Notebook (12), Pen (30)

So the compare function pulls out a.price and b.price, and subtraction does the rest.

🎛️ A sort dropdown that derives the list during render

Now let’s make it interactive, where the user picks a sort option and the list reorders. The trick is you don’t store the sorted list in state, you store only the choice and build the sorted list during render:

  • Keep one small piece of state, the sort key, like "price" or "name".
  • During render, copy the list and sort it based on that key.
  • This way the sorted list is derived state, always built fresh, never able to drift out of sync.

Here a dropdown changes the sort key, and the sorted list is computed right in the render.

import { useState } from "react";
function ProductList({ products }) {
const [sortBy, setSortBy] = useState("name");
// Derived during render - copy first, then sort
const sorted = [...products].sort((a, b) =>
sortBy === "price" ? a.price - b.price : a.name.localeCompare(b.name)
);
return (
<div>
<select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
<option value="name">Name</option>
<option value="price">Price</option>
</select>
<ul>
{sorted.map((p) => (
<li key={p.id}>
{p.name} - {p.price}
</li>
))}
</ul>
</div>
);
}

Let’s read it:

  • The state holds only sortBy, nothing else.
  • Each render we copy products, sort the copy by the chosen field, then map over the result to draw the list.
  • Change the dropdown and sortBy updates, so the component re-renders and the new order appears all on its own.

Sort key in state, sorted list in render

Store the choice, not the result. The sorted list comes from the original list plus the sort key, so deriving it each render keeps everything correct with no extra work.

🔃 Ascending vs descending

Most sort controls also let the user flip the direction, and you don’t need a second sort function for that:

  • Ascending means small to large, or A to Z.
  • Descending means large to small, or Z to A.
  • A neat trick is to compute the ascending compare, then multiply by -1 when you want descending.

Here a button toggles the direction, and one dir value decides which way the list runs.

function PriceList({ products }) {
const [asc, setAsc] = useState(true);
const dir = asc ? 1 : -1;
const sorted = [...products].sort((a, b) => (a.price - b.price) * dir);
return (
<div>
<button onClick={() => setAsc(!asc)}>
{asc ? "Price: Low to High" : "Price: High to Low"}
</button>
<ul>
{sorted.map((p) => (
<li key={p.id}>{p.name} - {p.price}</li>
))}
</ul>
</div>
);
}

So dir is 1 for ascending and -1 for descending. Multiplying the compare result by dir flips the whole order with one tiny change, and again we copy with spread before sorting.

🧩 What You’ve Learned

  • ✅ Sorting arranges the same list items into a chosen order, it does not change the items themselves
  • ✅ The compare function takes a and b and returns negative, positive, or zero
  • ✅ Use (a, b) => a - b for numbers and a.localeCompare(b) for text
  • .sort() mutates the original array in place, so it is dangerous on React state
  • ✅ Always copy first with [...items].sort(...) so your state array is never touched
  • ✅ Sort objects by a field with a.price - b.price or a.name.localeCompare(b.name)
  • ✅ Store only the sort key in state and derive the sorted list during render
  • ✅ Flip ascending and descending by reversing the compare or multiplying it by -1

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    Why is calling items.sort(...) directly on a React state array a problem?

    Why: sort changes the original array in place and returns that same array. On state, that mutates it directly, so React may not re-render and the data can go wrong. Copy first with [...items].sort(...).

  2. 2

    How do you sort an array of numbers from smallest to largest?

    Why: Copy the array first, then pass (a, b) => a - b. Plain sort() with no compare function sorts numbers as text, which gives the wrong order.

  3. 3

    Which compare function sorts an array of names alphabetically from A to Z?

    Why: You can't subtract two strings. localeCompare compares two strings and returns the negative, positive, or zero that sort needs, with A to Z order.

  4. 4

    When a dropdown lets the user choose a sort order, what should you keep in state?

    Why: Store only the sort key. The sorted list is derived state, built fresh each render from the original list plus the key, so it can never drift out of sync.

🚀 What’s Next?

You can now show a list in any order the user wants, the safe way, without ever mutating your state. That wraps up rendering lists. So far your apps mostly show data, but real apps also need to take input from the user. Next you’ll start learning how to collect what people type and choose.

React Forms

Share & Connect