Prompt
Build a grocery list app. Requirements:
- TextInput to add items + "Add" button
- Show list of items with toggle (strikethrough) and delete
- Validate: prevent empty items
- Simulate 1-second load delay on startup
- Auto-focus the input once loading finishes
Solution
CRUD-on-a-list is the smallest app that forces all three immutable update shapes, and interviewers watch for exactly those: add is spread-concat ([...prev, item]), toggle is map-with-spread (copy the one changed object, keep every other reference), delete is filter. All three go through functional updates (setItems(prev => ...)), so rapid taps can't clobber each other by closing over stale state.
Two boundary details carry the seniority signal:
- Validation at the mutation point:
if (!input.trim()) returninsideaddItemmeans no caller can insert whitespace items — the invariant lives with the setter, not scattered across the UI. Date.now()as the id, consumed bykeyExtractor. With delete in the feature set, index-as-key is actively broken: remove row 2 and every row below shifts its key, so React re-associates row state (the strikethrough, in-flight animations) with the wrong grocery item.
The focus requirement is the imperative escape hatch done correctly: the ref can't be focused on mount because during loading the TextInput isn't rendered at all — so a second effect keyed on loading focuses it once the input actually exists.
Red flag: items.push(...) followed by setItems(items). Same array reference — React bails out of the re-render and the UI silently stops updating. Mutation bugs in RN look like "the list doesn't refresh," not like errors.
Say it: "Add, toggle, delete are spread, map, filter through functional setState — new references on every change, stable ids so delete doesn't shuffle row identity."