Project · Full-Stack Development
Project: the Notes app frontend
The brief
Build the frontend of the Notes app against a mock API: an API client that handles cookies, JSON and errors; a reducer for the screen's state; form validation; a list that shows loading, empty and error states; optimistic saving that rolls back on failure; client-side routes; and timeouts for slow networks.
Why it matters
This is the everyday work of a full-stack or React developer: talk to the backend, keep the screen in sync with the data, and stay usable on a slow 3G connection. Every idea here maps straight to React (useReducer, components that render from state, React Router, TanStack Query), so you understand what those libraries do for you.
Skills you practise
- fetch with credentials, JSON and error handling
- a reducer for screen state
- client-side validation with aria-invalid
- loading, empty and error states
- optimistic updates with rollback
- hash-based routing
- AbortController timeouts
What you will have at the end
app.js for the Notes app: an API client, a reducer, validation, rendering of every state, optimistic saving, routing and timeouts, ready to point at the real backend.
How it is marked (rubric, 100 points)
| Works correctlyEvery check passes, and Run shows loading, saving, the rollback for a "fail" title and the note pages. | 40 |
|---|---|
| Good UX on slow networksLoading, empty and error states, instant optimistic saves, timeouts with a clear message. | 20 |
| Clean architectureOne API client, one reducer, render functions that only read state, no DOM code at the top level. | 25 |
| AccessibleErrors linked with aria-describedby and marked aria-invalid; status changes announced through role="status". | 15 |
0/7 steps done
Step 1 of 7
An API client
createApi(fetchFn, base) returns list(), create(note) and remove(id) that call base + /api/notes with credentials: "include" and JSON headers; a 204 gives null; an error status throws an ApiError with the status and the server's error message; a failed network throws an ApiError with status 0.
Put every backend call in one small client instead of scattering fetch() over the app. It sends cookies (credentials: 'include', needed when the API is on another origin and CORS allows it), sets JSON headers, and turns every failure into one kind of error the screens can show.
fetch only rejects when the network fails; a 400 or 500 still "succeeds", so check res.ok yourself. Read the message from the API's error shape { error: { code, message } }. fetchFn is passed in, so tests can use a fake one.
Run shows what your code prints. Check runs the step's tests. Ctrl+Enter checks too.