CodingNeed.

2 · Build one skill at a time · 18 MIN

Render a list and an empty state

Map data to elements and give each item a stable identity.

map creates one element per data item. A key tells React which item is which across additions, removals and reordering. Use the data’s stable id rather than a random value or a changing position. filter first selects the items to display; it does not mutate the original array. When nothing matches, render a useful sentence instead of an unexplained blank area. The search result is calculated from the query, so it does not need separate state or an effect.

Read the original data outside the component. Follow query through filter before map renders it. Type an unmatched term and inspect the conditional branch. Normalize the query once with trim().toLowerCase().

Before you start

Install Node.js 22.12+ (or a supported newer LTS). In a terminal run: npm create vite@latest codingneed-react -- --template react. Then cd codingneed-react, npm install, and npm run dev. Open the local URL printed by Vite. Replace src/App.jsx with ONE lesson example at a time. JSX renders a browser interface; it is not a plain console script.

File for this example: App.jsx

New words, explained

map
Transform each array item into another value.
key
A stable identifier among sibling elements.
derived
Calculated from existing inputs instead of stored twice.

Follow the example step by step

  1. Read the original data outside the component.
  2. Follow query through filter before map renders it.
  3. Type an unmatched term and inspect the conditional branch.
  4. Normalize the query once with trim().toLowerCase().

You are ready to move on when: Search is case insensitive. Searching spaces around SQL finds SQL. No-match and clear-search states both work.

Read the example

import {useState} from 'react';
const topics = [{id:'r', title:'React'}, {id:'s', title:'SQL'}, {id:'n', title:'Node.js'}];
export default function App() {
  const [query, setQuery] = useState('');
  const visible = topics.filter(topic => topic.title.toLowerCase().includes(query.toLowerCase()));
  return <main><h1>Find a topic</h1><label htmlFor="query">Search</label>
    <input id="query" value={query} onChange={event => setQuery(event.target.value)} />
    {visible.length ? <ul>{visible.map(topic => <li key={topic.id}>{topic.title}</li>)}</ul> : <p>No topics match. Clear the search to try again.</p>}
  </main>;
}
Check the expected output
All three topics appear initially. Searching sql shows SQL. Searching xyz shows the empty-state message.

Your challenge

Add Python with a stable id and make search ignore leading and trailing spaces.

Solution cost: Discuss the operations in this small example; rendering and I/O costs depend on the host. time · Proportional to the example’s retained data. space

Common trap

Random keys change identity on every render and can reset child state.

Study the project implementation
import {useState} from 'react';
const topics = [{id:'r', title:'React'}, {id:'s', title:'SQL'}, {id:'n', title:'Node.js'}];
export default function App() {
  const [query, setQuery] = useState('');
  const visible = topics.filter(topic => topic.title.toLowerCase().includes(query.toLowerCase()));
  return <main><h1>Find a topic</h1><label htmlFor="query">Search</label>
    <input id="query" value={query} onChange={event => setQuery(event.target.value)} />
    {visible.length ? <ul>{visible.map(topic => <li key={topic.id}>{topic.title}</li>)}</ul> : <p>No topics match. Clear the search to try again.</p>}
  </main>;
}

Further reading: Official documentation

Next lesson: Share state between two components

Essential cookies keep your account signed in. Optional analytics is not configured on this site. Your choice does not affect access to lessons.

Read the Privacy Policy. You can change this choice in the footer.