Chapter 9 of 20 · React for Beginners — Complete Free Tutorial
Rendering Lists and Keys
Feeds, tables, carts, comment threads — most of the React for Beginners interface is arrays rendered as lists. JavaScript's map() turns data into elements, but React needs one extra piece of information to do its job efficiently: a stable key for every item. This chapter covers mapping, keys, filtering, empty states, and the mistakes that cause mysterious rendering bugs.
Mapping data to elements
Call map() inside braces and React renders whatever array comes back. Each element should be a component or a tag:
const tasks = [
{ id: 1, title: 'Read the chapter' },
{ id: 2, title: 'Type the examples' },
{ id: 3, title: 'Do the checklist' },
];
function TaskList() {
return (
<ol>
{tasks.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ol>
);
}
Expected output:
1. Read the chapter
2. Type the examples
3. Do the checklist
You can also render expressions like tasks.length alongside the list, and map to full components for richer rows.
Why React needs keys
When state changes, React reuses existing DOM nodes instead of recreating them — it matches old items to new ones. The key prop is how it identifies which is which. Without keys, React falls back to position-based matching, which quietly misbehaves once the list reorders.
| Key strategy | Stable? | When it is safe |
|---|---|---|
task.id from your data | Yes — unique and permanent | Always preferred |
| Array index | No — shifts when items reorder | Only for truly static lists that never insert, delete or sort |
Math.random() / Date.now() | Changes every render | Never — forces a full remount each update |
If your data has no id, generate one when the item is created (crypto.randomUUID()), not while rendering.
Keys identify, they do not display
A key is invisible on screen — it is metadata for React. That surprises people who try to show a sequence number. Render the number explicitly instead of smuggling it into the key:
{items.map((item, index) => (
<li key={item.id}>
<span>#{index + 1}</span> {item.name}
</li>
))}
Expected output: rows showing #1, #2, #3… where the visible number comes from index and the identity used by React comes from item.id. Keys must also be unique among siblings — duplicates in the same list trigger a console warning.
Filtering and transforming
Do heavy work before the JSX when you can; render the result. Arrays give you a clean pipeline with map, filter and reduce:
const done = tasks.filter((t) => t.done);
const labels = tasks.map((t) => t.title.toUpperCase());
// or inline when it stays readable:
<ul>
{tasks
.filter((t) => t.priority === 'high')
.map((t) => <li key={t.id}>{t.title}</li>)}
</ul>
Expected output: only high-priority rows in the list; filtering out everything renders nothing at all — which is why the next section matters.
Empty states
An empty list should say so. Combine the length check with && or a ternary so users know the absence is a state, not a failure:
{tasks.length === 0
? <p className="empty">No tasks yet — add your first one.</p>
: <ul>{tasks.map((t) => <li key={t.id}>{t.title}</li>)}</ul>}
Expected output: with an empty array, the friendly placeholder appears and no <ul> is created; once items exist the list takes over. This "zero, one or many" handling is a pattern you will reuse in every data view you build.
Lists of components
Rows are usually their own component, which keeps props tidy and lets each row hold its own state later:
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
Expected output: one card per product, each receiving its own product prop. Remember: the key goes on the element in the list — <ProductCard key={…} /> — not inside the component.
Common mistakes
- Forgetting
key— React warns "Each child in a list should have a unique key" and loses track during updates. - Using the array index as a key — edits, deletions then reorder or overwrite the wrong rows and their state.
- Picking keys that change every render — random keys remount the whole list, destroying focus and state.
- Pushing into an array instead of mapping —
arr.push(<li />)mutates and returns a number; usemapand render its return value. - Duplicate keys among siblings — ids must be unique within the list you are rendering.
Practice checklist
- Render an array of five strings as an unordered list with keys.
- Add, remove and reorder items in dev tools and watch for warnings about keys.
- Convert one list to index keys, delete an item mid-list, and observe the bug.
- Display both a visible sequence number and a stable key on each row.
- Add an empty-state message that appears exactly when the array is empty.
Lists show data — forms let users change it. Continue with Forms and Controlled Components.
Frequently Asked Questions
React is a JavaScript library for building user interfaces from reusable components. It manages how your UI updates when data changes, so you describe what the screen should look like and React performs the efficient DOM updates. It is one of the most in-demand frontend skills and powers products from Netflix to Instagram, which makes it an excellent next step after core JavaScript.
You should be comfortable with functions, arrays and objects (map, filter, spread), destructuring, modules and basic async/await. React itself is written in plain JavaScript — JSX is a syntax layer on top. If any of those feel shaky, revisit the JavaScript fundamentals first and this series will go much faster.
React is a library focused only on the UI layer — you choose your own router, data layer and tooling. Angular is a full framework that ships routing, forms, HTTP client and testing conventions as one opinionated package. React's flexibility and huge ecosystem make it the common choice for teams that want to compose their own stack.
A component is a reusable function that returns UI. It receives input through props, keeps private data in state, and can render other components. Pages are composed by nesting small components (navbar, card list, footer) instead of one giant file, which keeps code testable and easy to change.
JSX is a syntax extension that looks like HTML but compiles to React's create() calls — it is a description of UI, not markup the browser parses. Small differences matter: class becomes className, style takes an object, and you embed JavaScript expressions inside curly braces. Once compiled, React turns that description into real DOM nodes.
Props are inputs passed from a parent — they are read-only inside the child, like function arguments. State is private, mutable data owned by a component; updating it triggers a re-render. The rule of thumb: values that come from outside the component are props; values that change over the component's lifetime are state.
Rendering means calling your component function to produce a description of the UI for the current state and props. It does not always mean DOM work — React compares the new description with the previous one (reconciliation) and applies only the differences to the real DOM. This is why updating state 100 times does not mean 100 full page rebuilds.
An effect runs after the component has committed to the screen — after render, not during it. You list dependencies in an array, and React re-runs the effect only when one of them changes. If the effect returns a cleanup function, React runs it before the next re-run and on unmount, which is where you cancel timers, abort fetches and remove listeners.
Use Vite. Create React App is effectively in maintenance mode, while Vite starts instantly (native ES modules) and builds fast, and it is the default choice for new React projects. This series uses Vite for every example — you only need Node.js installed to follow along.
React apps compile to static files (HTML, CSS and JavaScript), so any static host works: Netlify, Vercel, GitHub Pages or Cloudflare Pages. Run the production build, upload the dist folder, and configure a single-page-application fallback so unknown paths load index.html — routing chapters explain why that fallback is required.