Skip to content

Lists and Keys

Most UIs involve lists — income sources, spending categories, transactions. React renders lists by mapping an array to JSX. The only rule: every item needs a key prop.

const categories = [
{ id: 'housing', name: 'Housing', budgeted: 1200 },
{ id: 'food', name: 'Food', budgeted: 600 },
{ id: 'transport', name: 'Transportation', budgeted: 400 },
];
export default function CategoriesGrid({ categories }) {
return (
<div className="categories-grid">
{categories.map(cat => (
<CategoryCard key={cat.id} category={cat} />
))}
</div>
);
}

Array.prototype.map transforms each item in the array into a JSX element. React renders the resulting array of elements.

When React re-renders a list, it needs to know which items changed, which were added, and which were removed. Without keys, React re-renders the entire list on every change — slow, and it causes bugs with form inputs that lose focus or animations that reset.

Keys let React match each rendered element to the item it came from across re-renders. When an item is removed, React removes only that element. When an item is reordered, React moves the DOM node rather than recreating it.

The key must be unique among siblings — not globally unique. It must be stable — the same item should always get the same key. Using the database ID or a UUID is correct. Using the array index is a last resort (it causes bugs when the order changes).

{items.map((item, index) => (
<li key={item.id}>...</li>stable, unique
))}
{items.map((item, index) => (
<li key={index}>...</li>breaks when list reorders
))}

The key goes on the outermost element returned from map, not on a child inside it:

{categories.map(cat => (
<CategoryCard key={cat.id} category={cat} />
))}
{categories.map(cat => (
<CategoryCard category={cat}>
<div key={cat.id}>...</div> ✗ wrong level
</CategoryCard>
))}

The key is not a prop — it is metadata for React. The CategoryCard component cannot read props.key.

ZeroBudget has transactions nested inside categories. Map the outer array, then map the inner array:

{categories.map(cat => (
<CategoryCard key={cat.id} category={cat}>
{transactions
.filter(tx => tx.categoryId === cat.id)
.map(tx => (
<TransactionRow key={tx.id} transaction={tx} />
))}
</CategoryCard>
))}

Filter the transactions for each category before mapping them. This is pure JavaScript — React does not need special syntax for it.

  1. Create a CategoriesGrid component that accepts a categories array prop and maps it to <CategoryCard> components.
  2. In CategoryCard, render the category name and budgeted amount from the category prop.
  3. Create a defaults.js file in src/data/ with the 8 ZeroBudget default categories (Housing, Transportation, Food, Utilities, Clothing, Personal, Savings, Giving — each with a unique id, name, and budgeted: 0).
  4. Import the defaults in App.jsx and pass them to CategoriesGrid. Confirm all 8 cards render.
  • Use Array.prototype.map to render lists — it returns an array of JSX elements.
  • Every element returned from map needs a unique, stable key prop.
  • Keys go on the outermost element from map, not on children inside it.
  • Use item IDs for keys — not array indexes.
  • The key prop is metadata for React; the component cannot read it.