AniUI Academy

Build a sortable data table

mediumUI ComponentsHigh freq+200 XP

Render a table of people with columns Name, Age and City. Clicking a column header sorts the rows by that column.

A header cycles through three states on repeated click: ascending → descending → none (original order). Only one column is sorted at a time — clicking a new header resets the previous one.

Requirements

  • Each column header is a `<button data-testid="header-{name|age|city}">`.
  • Each sortable header reflects state via `aria-sort="ascending"`, `"descending"` or `"none"`.
  • Rows are `<tr data-testid="row">` elements rendering `.name`, `.age`, `.city` as plain text cells.
  • First click sorts ascending; second click on the same header sorts descending; third click returns to the original order.
  • Clicking a different header resets the previous header's `aria-sort` to `"none"` and starts fresh on the new column.
  • Sorting must be stable — two rows with the same sort key preserve their original relative order.

Where it's been asked

  • Google
  • Microsoft
  • Amazon
  • Atlassian
  • LinkedIn
  • Stripe

From public interview reports — a build very like this one has shown up in a round at these companies. Not an exhaustive list.

Stuck? The react course covers the ideas this problem is built on.

import { useState } from "react";

const PEOPLE = [
  { name: "Priya", age: 29, city: "Bengaluru" },
  { name: "Omar", age: 34, city: "Dubai" },
  { name: "Ana", age: 29, city: "Lisbon" },
  { name: "Kenji", age: 41, city: "Tokyo" },
  { name: "Zara", age: 29, city: "Karachi" },
];

export default function App() {
  const [sort, setSort] = useState({ column: null, direction: "none" });

  // TODO: derive sortedRows from PEOPLE based on sort.column + sort.direction.
  // Remember: stable sort, three-state cycle, only one active column.
  const sortedRows = PEOPLE;

  return (
    <table style={{ fontFamily: "sans-serif", borderCollapse: "collapse", padding: 16 }}>
      <thead>
        <tr>
          {["name", "age", "city"].map((col) => (
            <th key={col}>
              <button
                data-testid={`header-${col}`}
                aria-sort="none"
                style={{ padding: 8, cursor: "pointer" }}
              >
                {col.toUpperCase()}
              </button>
            </th>
          ))}
        </tr>
      </thead>
      <tbody>
        {sortedRows.map((person, i) => (
          <tr key={i} data-testid="row">
            <td style={{ padding: 8 }}>{person.name}</td>
            <td style={{ padding: 8 }}>{person.age}</td>
            <td style={{ padding: 8 }}>{person.city}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

Ready to check it?

5 tests run against your rendered component, right here in your browser. Sign in to claim the XP when you pass.

Need a nudge?

Reveal a conceptual hint or the reference solution. Both were written and reviewed with the problem — no runtime magic.

Discussion

Sign in to post
Sign in to join the conversation.Sign in

No comments yet. Be the first to say something.