React project ideas: 12 builds that each teach one concept
Good React projects are small apps built around one concept: a shopping list for state and keys, a multi-step form for lifting state, a search box for effects and loading states, an expense tracker for reducers and context. Start with those, then build a comment thread with optimistic updates, a virtualized list or a schema-driven form. Below are 12 projects in three levels, each with the concept it teaches, a first version, a stretch goal and a time estimate.
What makes a good React project?
A good React project makes you decide where state lives. That decision is most of React. A to-do list does not force it, because everything fits in one component. A multi-step form does, because three components need the same data. Pick projects where the answer to “who owns this value?” is not obvious on day one.
The official guide Thinking in React gives a five-step method: break the interface into components, build a static version, find the minimal state, decide where it lives, and add the data flow back. Use it on every project below. The first prompt in each entry follows the same order.
| Concept | Project | Level |
|---|---|---|
| State, lists and keys | Shopping list grouped by aisle | Beginner |
| Lifting state up, controlled inputs | Multi-step sign-up form | Beginner |
| Composition, context, accessibility | Tabs and accordion kit | Beginner |
| Effects, loading and error states | Book search with cancel | Intermediate |
| URL as state | Filterable dashboard | Intermediate |
| Reducers and context | Expense tracker with a hand-made chart | Intermediate |
| Optimistic updates | Comment thread that never waits | Intermediate |
| Refs, cleanup, saving | Notes app with autosave | Intermediate |
| Rendering cost | Virtualized list of 100,000 rows | Advanced |
| Custom hooks and tests | Hook library with tests | Advanced |
| Recursion and schemas | Schema-driven form builder | Advanced |
| External stores | Live poll with useSyncExternalStore | Advanced |
App name: Who uses it (one sentence): Screens (list each): Components I expect (a rough tree): State, and which component owns each piece: Data that comes from outside (API, URL, storage): States every screen needs: loading, empty, error I will not build in version one (three things):
Which React projects suit a beginner?
Beginner React projects have no server and no routing. They exist to make you comfortable with components, props and state, and with the one rule that confuses most people at first: do not change state in place, replace it.
01 Shopping list grouped by aisle
A list where items belong to an aisle, tick off as you shop, and move to a “done” group at the bottom.
- Stack
- React, Vite, plain CSS.
- Teaches
- State, rendering lists with keys, derived values, conditional rendering.
- First version
- Add an item with an aisle, tick it, remove it. Items show grouped by aisle with a count beside each group.
- Stretch goal
- Remember the last 50 items for quick re-adding, and copy the list as text.
- Time
- 3 to 5 hours.
Store the flat list of items and compute the groups while rendering. Do not keep a second, grouped copy in state, because now two things must change together and one day they will not. Give each item a stable id for its key. An array index as a key makes ticked boxes jump to the wrong item when you delete one.
02 Multi-step sign-up form
A three-step form (account, profile, review) with a progress bar, validation on each step and a summary at the end.
- Stack
- React, Vite, plain CSS.
- Teaches
- Sharing state between components, controlled inputs, validation, disabled states.
- First version
- Next is disabled until the step is valid. Back keeps what you typed. Review shows everything and a Submit button that logs the data.
- Stretch goal
- Keep progress through a reload, focus the first invalid field, and announce errors to screen readers.
- Time
- 5 to 8 hours.
Put the form data in the parent and pass each step its slice and a change handler. The temptation is to let each step own its own state, which loses data when you go back. Write the validation as pure functions that take the data and return a list of problems, so you can test them without rendering anything. The board's RSVP that tells you who is actually coming has a form like this at its core.
Build a three-step sign-up form in React with Vite. No backend, no UI library. Steps: 1 Account (email, password), 2 Profile (display name, country drop-down), 3 Review. State: one object in the parent component. Each step gets its slice and an onChange. Rules: email must contain @, password at least 10 characters, display name 2 to 32 characters. Validation lives in a separate pure function file. Behavior: Next is disabled until the step is valid. Back keeps typed values. Submit logs the object to the console. Do not build: routing, API calls, password strength meter. Done when: I can fill step 1, go to 2, go back and see step 1 unchanged, and there are tests for the validation file.
03 Tabs and accordion kit
Two reusable components, Tabs and Accordion, that other code composes with children instead of configuring with a long props object.
- Stack
- React, TypeScript if you know it, plain CSS.
- Teaches
- Context, children, compound components, ARIA roles, keyboard handling.
- First version
- <Tabs><Tab label=...>...</Tab></Tabs> and <Accordion><Item title=...>...</Item></Accordion>, both working with mouse and keyboard.
- Stretch goal
- Controlled and uncontrolled modes, a single-open versus multi-open accordion, and a demo page that shows every state.
- Time
- 6 to 10 hours.
The parent holds which item is open and shares it through context, so the children stay plain. For keyboard behavior, copy the pattern from the WAI-ARIA Authoring Practices: arrow keys move between tabs, Home and End jump to the first and last. Test it by unplugging your mouse.
Which React projects teach effects, data and forms?
Here is where apps meet the outside world: the network, the URL, storage. Most bugs in intermediate React come from effects used for things that were never side effects. Read You Might Not Need an Effect before the first project in this group and again after the last.
01 Book search with cancel
A search box that lists books from an open API as you type, with loading, empty and error states and no stale results.
- Stack
- React, Vite, fetch, an open books API.
- Teaches
- useEffect and its cleanup, AbortController, request races, useDeferredValue.
- First version
- Type, wait 300 milliseconds, show 10 results with cover and author. A message for no results and one for a failed request.
- Stretch goal
- Cache by query, page results, and compare your effect with a data-fetching library.
- Time
- 6 to 9 hours.
Write the effect so its cleanup cancels the request. Then type quickly and watch the network panel: you should see earlier requests cancelled. Then write the same feature a second time using a data-fetching library and note which bugs you no longer own.
02 Filterable dashboard with the URL as state
A table of records with filters, sort and page number that all live in the URL, so a filtered view can be bookmarked and shared.
- Stack
- React, a router, a JSON file of 500 sample rows.
- Teaches
- Search params, derived data, memoization when it is needed, back-button behavior.
- First version
- Text filter, a category drop-down, sort by one column, 25 rows per page, all read from and written to the query string.
- Stretch goal
- Debounce typing so the history is not flooded, and a “reset filters” link that is only visible when filters are set.
- Time
- 6 to 10 hours.
The point is that the URL is the single source of truth, so there is no filter state to keep in sync with it. Compute the visible rows from the URL and the data on each render. Measure before you add useMemo: with 500 rows you will not need it, and knowing that is the lesson.
03 Expense tracker with a hand-made chart
Add, edit and delete expenses by category, see a monthly total and a bar chart you draw in SVG yourself.
- Stack
- React, useReducer, context, SVG.
- Teaches
- Reducers, splitting state from actions, context providers, money as integer cents.
- First version
- Actions added, edited, deleted. Totals per category. A bar chart whose bar heights are computed, not typed in.
- Stretch goal
- Undo through the reducer, CSV export, and a second reducer test file that replays a list of actions.
- Time
- 8 to 12 hours.
Worked example for your tests: expenses of 12.50, 8.20 and 30.00 are stored as 1250, 820 and 3000 cents and total 5070, shown as 50.70. A reducer is a plain function from (state, action) to a new state, so test it with no React at all. Drawing the chart yourself, even once, shows you what charting libraries do for you.
04 Comment thread that never waits
Post a comment and see it at once, then watch it settle or roll back when the server answers.
- Stack
- React, a mock API with random delay and failure.
- Teaches
- useOptimistic, pending and failed states, retries, ordering.
- First version
- A form and a list. A new comment appears greyed out instantly. It turns solid on success and shows a Retry button on failure.
- Stretch goal
- Delete and edit with the same pattern, and a test that forces the mock API to fail every third call.
- Time
- 6 to 9 hours.
Write the mock API first and make it fail 30 percent of the time, otherwise you never see the failure path. Decide what the user sees when the same comment is sent twice after a retry.
Build a comment thread in React with Vite. No real backend: write a fake api.js with postComment(text) that waits 800 to 1500 ms and rejects 30 percent of the time. Behavior: submitting adds the comment to the top immediately, styled as pending. On success it becomes normal. On failure it shows "Failed to send" and a Retry button that resubmits the same text. Use React's useOptimistic for the pending state. Do not build: accounts, replies, editing. Done when: I can submit 10 comments, see some fail, retry them, and no comment is ever shown twice. Add tests for success, failure and retry using React Testing Library.
05 Notes app with autosave
A notes list and an editor that saves a second after you stop typing and tells you when it has.
- Stack
- React, IndexedDB through a small wrapper, or Web Storage to start.
- Teaches
- Refs, effect cleanup, debounced saves, a “saved” indicator, unsaved-changes warnings.
- First version
- Create, select, edit and delete notes. Edits save after 1 second of silence. Reloading the page keeps everything.
- Stretch goal
- Search across notes, a “Saving…” state that is correct if two saves overlap, and export all notes as one Markdown file.
- Time
- 8 to 12 hours.
The easy bug: the user switches notes during the one-second wait, and the save lands on the wrong note. Capture the note id when the timer starts, and clear the timer in the effect cleanup. This is a clean example of why cleanup functions exist.
Which React projects are hard in a useful way?
These four are harder because the naive version stops working, not because they need more libraries. Each starts with an obvious solution and a measurable reason it fails. Write the obvious one first, break it, then fix it.
01 Virtualized list of 100,000 rows
A scrolling list that only renders the rows on screen, so 100,000 items feel as light as 100.
- Stack
- React, plain CSS, the browser’s performance panel.
- Teaches
- Rendering cost, scroll position maths, keys, refs, measuring before optimizing.
- First version
- A fixed row height of 40 pixels, a container of 600, rows positioned from scroll offset, with five extra rows above and below.
- Stretch goal
- Variable row heights, jump to row N, and keyboard navigation that scrolls the active row into view.
- Time
- 10 to 16 hours.
Worked example: 100,000 rows at 40 pixels is a 4,000,000-pixel tall scroll area. A 600-pixel window shows 15 rows. With 5 extra rows on each side you render about 25 elements instead of 100,000. Build the naive version first and record how long it takes to render. That number is your reason to build the real one.
02 Hook library with tests
A small package of custom hooks such as useDebounce, useMediaQuery, useOnClickOutside and useStoredState, each with tests and a demo.
- Stack
- React, TypeScript, Vitest, React Testing Library.
- Teaches
- Custom hooks, cleanup, stable function identities, testing time and events.
- First version
- Four hooks, a test file for each, and one demo page. Fake timers for useDebounce.
- Stretch goal
- Publish to a private registry or a local tarball, and write an API table in the README.
- Time
- 10 to 15 hours.
A hook is just a function that calls other hooks, which is easy to say and hard to feel until you extract one. Start from a component that works, find the repeated logic, and move it out. Then test the cases where a hook leaks: unmounting mid-timer, and changing arguments between renders.
Create a TypeScript package of four React hooks: useDebounce(value, ms), useMediaQuery(query), useOnClickOutside(ref, handler), useStoredState(key, initial) that persists JSON in Web Storage and falls back to memory if storage is unavailable. For each: a file, a test file with Vitest and React Testing Library, and a section in README.md with a one-line example. Rules: every listener and timer is cleaned up on unmount. No dependencies beyond React. Do not build: SSR support, a docs site. Done when: the tests pass, including one that unmounts during a pending debounce and asserts nothing fires afterwards.
03 Schema-driven form builder
Describe a form as JSON (fields, types, rules) and render it, including nested groups and repeatable sections.
- Stack
- React, TypeScript, a validation library of your choice.
- Teaches
- Recursion in components, discriminated unions, dynamic state shape, error mapping.
- First version
- Text, number, select and checkbox fields, required and min/max rules, and one nested group.
- Stretch goal
- Show a field only when another has a given value, repeatable rows with add and remove, and a live JSON preview of the output.
- Time
- 12 to 20 hours.
A component that renders itself for nested groups is the heart of it. Decide how the output path works for a field inside a repeated group before you write the renderer, for example “orders.2.quantity”, because the validation errors must come back on the same paths.
04 Live poll with an external store
A poll whose votes update on every open tab, using a small store outside React and a hook that subscribes to it.
- Stack
- React, useSyncExternalStore, the BroadcastChannel API.
- Teaches
- Subscriptions, snapshots, tearing, why a store can live outside components.
- First version
- A store with subscribe, getSnapshot and vote. Open two tabs and a vote in one shows in the other.
- Stretch goal
- Swap BroadcastChannel for a real WebSocket server, and close the poll at a set time.
- Time
- 8 to 12 hours.
getSnapshot must return the same object until the data really changes, or React warns about an infinite loop. That rule is the lesson. The board has a related idea, A group poll that closes itself, if you want a real use for the result.
Where do I find a React project worth building?
Pick a problem from your own week, or from the board at Post your build ideas, where people post app ideas they will never build. Browse all ideas or the productivity category, choose one that fits on two or three screens, and copy its generated build prompt into Claude Code, Cursor or Lovable. Then use the checklist above to review what comes back. Posted does not mean validated. Ask one real person whether they would use it.
React alone is rarely the whole job. When a project needs a database and sign-in, move to the full stack project ideas. For the broader mix across languages, see coding project ideas.
Frequently asked questions
What React project should I build first?
Build a shopping list grouped by aisle. It has state, a list with keys and values derived from state, and it needs no server or router. Once that works, try a multi-step form, which makes you decide which component owns the data.
How many React projects do I need before applying for jobs?
There is no magic number. Two or three finished projects with tests, a deployed link and a README that explains your decisions are enough to talk about. Choose projects that show different things: one with data fetching, one with complex state, one with a tricky interface.
Should I use TypeScript in my React projects?
Start without it for the first one or two projects so you learn React itself. Then add it. It makes components with many props and reducers with many actions much easier to change safely, and most React jobs expect it.
Do I need Redux or another state library for these projects?
No. State, useReducer and context cover every project on this page. Reach for a library only after you can name the specific problem it solves, such as sharing frequently changing state across a large tree. Building without one first shows you what you would be getting.
Can I build these React projects with an AI coding tool?
Yes, and the prompts here are written for that. Keep the first prompt to one screen, ask for tests, and read the state logic yourself. If you cannot say where each piece of state lives and why, ask the tool to explain, then rebuild one component by hand.
What is the difference between a React project and a full stack project?
A React project runs in the browser and may call outside APIs. A full stack project also includes a server and a database that you build and own, with sign-in, validation and deployment. Build several React projects first, then add a server.
Created with AI assistance and edited for this site. Examples are illustrative and do not represent customer research. About the publisher.