Build an autocomplete
mediumUI ComponentsHigh freq+200 XP
Build a search box for a small, fixed list of fruit. Typing filters the list to items whose name starts with what has been typed so far, case-insensitively.
Picking a suggestion fills the input with it and closes the list.
Examples
// Type "ap" into the input
// → <li>Apple</li>, <li>Apricot</li>
// Type "zzz"
// → the <ul> is empty (no "no results" row)
// Click <li>Apple</li>
// → input value is "Apple", no <li> renderedPrefix match, case-insensitive; no stale list once a suggestion is picked.
const suggestions =
query.length === 0 || selected
? []
: FRUITS.filter((f) =>
f.toLowerCase().startsWith(query.toLowerCase())
);Derive the list during render — keep `query` as the single source of truth; a `selected` flag collapses the list after a pick.
What it has to do
- The fruit list is exactly: Apple, Apricot, Banana, Blueberry, Cherry, Coconut, Date, Grape.
- There is exactly one `<input>` on the page.
- Each suggestion renders as an `<li>` containing the fruit's full name as its text.
- An empty input shows no suggestions.
- Typing text that matches nothing shows no suggestions (not an error, not stale results).
- Clicking a suggestion sets the input's value to that suggestion and closes the list — no `<li>` elements remain.
Notes
- A real autocomplete would debounce the filter when the dataset is large or remote — here it's an in-memory array of 8, so filtering per keystroke is free.
- Accessibility: the input should be a `role="combobox"` wired to the listbox via `aria-controls` / `aria-activedescendant`; the test harness only checks DOM structure, but production code needs it.
- Match-strategy matters — `startsWith` is intentional. `includes` would surface noise ("Apple" matching "ple"), and fuzzy ranking is a separate problem.
Stuck? The react course covers the ideas this problem is built on.
import { useState } from "react"; const FRUITS = ["Apple", "Apricot", "Banana", "Blueberry", "Cherry", "Coconut", "Date", "Grape"]; export default function App() { const [query, setQuery] = useState(""); // TODO: compute the fruit whose name starts with `query`, case-insensitive. // An empty query should produce no suggestions. const suggestions = []; return ( <div style={{ fontFamily: "sans-serif", padding: 16, maxWidth: 320 }}> <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search fruit..." style={{ width: "100%", padding: 8, fontSize: 14, boxSizing: "border-box" }} /> <ul style={{ listStyle: "none", padding: 0, marginTop: 4 }}> {suggestions.map((fruit) => ( <li key={fruit} // TODO: clicking a suggestion should fill the input with it and // close the list. style={{ padding: 8, cursor: "pointer", borderBottom: "1px solid #eee" }} > {fruit} </li> ))} </ul> </div> ); }
- 1.The fruit list is exactly: Apple, Apricot, Banana, Blueberry, Cherry, Coconut, Date, Grape.
- 2.There is exactly one `<input>` on the page.
- 3.Each suggestion renders as an `<li>` containing the fruit's full name as its text.
- 4.An empty input shows no suggestions.
- 5.Typing text that matches nothing shows no suggestions (not an error, not stale results).
- 6.Clicking a suggestion sets the input's value to that suggestion and closes the list — no `<li>` elements remain.
5 automated assertions run against your rendered component. The exact assertion names are hidden so you build to the behaviour, not to the test.
5 tests · +200 XP
Discussion
Sign in to postSign in to join the conversation.Sign in
No comments yet. Be the first to say something.