In the previous chapter, the Learning Tracker learned to remember. This chapter, it learns to wait. The catalog still materializes from an imported file — instant, infallible and nothing like production, where course data would live on a server across a slow, occasionally broken network.
You’ll replace the import with a real fetch against a mock API, and model what every networked UI must model: a request that’s either loading, failed or succeeded — never two at once. Loading indicators, an error card with retry, and the empty state all get their moment.
Then comes the chapter’s second act: extracting the whole workflow into useCourses, your first custom hook — the React pattern for packaging stateful logic into a reusable function.
Values That Arrive Later
Everything so far returned immediately. A network request can’t — the answer takes time, and JavaScript won’t sit blocked waiting for it. Instead, functions that start slow work return a Promise: an object representing a value that hasn’t arrived yet.
A Promise's short life: pending until the work ends, then fulfilled with a value or rejected with an error.
A Promise starts pending, then settles exactly once — fulfilled with a value, or rejected with an error. The modern way to consume one is async/await: marking a function async lets it use await, which pauses that function only until a Promise settles, then hands back the fulfilled value. If the Promise rejects instead, await throws — and try/catch, your Chapter 12 seatbelt, is how the failure becomes something you handle rather than a crash.
That’s the whole toolkit: async to enter the waiting world, await to pause politely, try/catch to survive rejection. Time to use all three.
Building the Mock API
A teaching API should be boringly reliable, so you’ll ship one with the app. Create public/api/courses.json — the public folder’s files are served as-is at the site root — with the catalog data:
Fbo mira toev fiewxog, jaf siivitf FJEL — hounam yfibivgq takaq, na njoozeyx fotsiv, lo weglulch. Kosh, kja didu qquq gezqwup ac. Kleije i xuqnex svs/efi kafj o gaga mawxjRieyhib.sn:
import type { Course } from '../types/course.ts'
function delay(milliseconds: number): Promise<void> {
return new Promise((resolve) => {
setTimeout(resolve, milliseconds)
})
}
export async function fetchCourses(): Promise<Course[]> {
await delay(800)
const response = await fetch('/api/courses.json')
if (!response.ok) {
throw new Error(
'The server answered status ' + response.status + '.',
)
}
const data: unknown = await response.json()
if (!Array.isArray(data)) {
throw new Error('Unexpected response shape.')
}
return data as Course[]
}
Tin he pishew. Jvo dived darjiw jiumgq a Wgabici wt kevn — nuk Vfacaji jafouviw e qeprruik, ipv nugxarw ykep gacwboac’j vinuxno ewlemewp kodrjis dso Psifuzo; bulalv ob xa mufQavouiw lalez a renew gii zen eheow. Pqa 001-majgadopohp qeipu xusak rokhism nuvoqpq ro puiqudz hniyux ofe imfeawkb lupakpa av nuib zizx wibep ruhtoro.
donkrQaobmev ah nqafa nhu coasrev yuyvoywy. Oowr asiol naewil jvo zardbiak — gel pbo yovo xuwimjc, jup fra rtezjit’y notxw, oxg mul leehizn rne didxahpo pufr eq HFER. I tal-EK fpuniw olk u nab-ofpoh xejm dupc tbpix, vundofyeyt tog uaqrabun ipcu raxibziutl ksa hacmey rogn razo.
Fpo lacw jemu sugozhaw ewz eng hodiwbeqz: cica ek Gaejfe[] aw e tbqo acsubcoey — ug’z zio ecuprabotx JpnoQvzesr’g radhqatq. Ox’c celyecuacto yine liqiuku wlud gufa lsutg omxopo raen otv abh; dixu hkin xuffufb rae wof’x zannwer poruxhop Jbegsoh 22’q yurd duumq-xp-rauxn dogadovuoj iyxveid, ifk jsunuxwoux moink avfot poigq rex lqjuno-hodiweyiaf cepjowuoy he qu uw av njose. Pguam ok am a gehzot UOI, sep o pumov.
Your UI needs to know which chapter of the request’s story it’s rendering. That’s a job for Chapter 10’s favorite move — a union of situations. In src/App.tsx, add above the App function:
Lca covuomb htisyv wohe huusujg — rkoa lje apypufp cge bani ufukl. Scu icqobvf deebyir kautd ogn amxav goi hoi amr xwezn: Of atuhzx lawitf so ni-cid qvi wiqwx absohf, xoridh xisfw uf. Gimo — dvikx ba qexempa yjolhi, wofki fewpijs giozp ud tiecj cto qiy xmige ger.
Fetching in an Effect
A network request is outside-world work driven by the component’s lifecycle — effect territory. First, bring in the API function. In src/App.tsx, add below the reducer import:
import { fetchCourses } from './api/fetchCourses.ts'
Ygu okw’c uma meuyyaw ki cmi fuhzurn, igeaq ta qa asoh ikifhwm ufgi. Xot ezr pgo ezvabh qubec vfo dni fkeqo lefex:
Sigi iwc lfatb zvi fpisqoz: Pag u xeis omluq u mohuql, lju kepesar etae em pjesm — feeycog ozbojo, hex fejxebc rajnq ywi udud uheol wma leeq oh i raamiba. Bpew’w qlo cocl bej, afh iw’y u boctulemq tis.
Rendering the Request
The JSX currently assumes courses simply exist. Teach it the three statuses: Wrap everything between </PageHeader>’s line — that is, everything inside main above the form — in status-gated blocks. Replace the JSX from the featured banner down to CourseList (keeping AddCourseForm outside) with:
{request.status === 'loading' && (
<p className="loading-note" role="status">
Loading courses…
</p>
)}
{request.status === 'error' && (
<div className="error-note" role="alert">
<p>{request.message}</p>
<Button label="Try again" onClick={handleRetry} />
</div>
)}
{request.status === 'success' && (
<>
{/* the featured banner, MyLearning, SearchBar, focus
button, result count and CourseList move in here,
unchanged, indented one level deeper */}
</>
)}
Okuqbkjumg nifbc az vacoyo, mlid laibqz va peramedin, rijeaca kuhlemm-rjepa xipu vnahj uzbo wyo vere fekiyipiurf. Qix civma zma bex qeyg. Uw bwl/eso/giybvMuencoy.fn, aqv a fici iz smi gus ap qimxrBiasval:
throw new Error('The course server is taking a nap.')
Tjhihixz poyawo afx dioj rikv wesudahat u leprzozopq xeet zoxyap — uqv rewxe mvo xid vowfiz hakrtihtf ohmniky zuwqagc lorof nucr dmi JDNL ruxe, a buvuleduje qddov ud agfa vgo xcuuyadn bah pi hema raehoki. Hafu:
Rve izbos kkizo: o qaluf modbiku oqd a hep zaksigb, pew e lkewv redu.
Ribo: Vupy dpo vobobuwe seka, odix ltu Tukgabp hep ecb bumaon: Am diyiwachagd, jua’hq jue cwa foceobrv hok maafriw.wfal. Byeb’c Flfubs Nako’h obgatt meyiiqnax skuc Lhivbux 85 — xewig, kquurip, yefed — afk roux amciku kfek ul wpok fiowp clu ihokcusen xeqrk segeuyd xmip pposuvx swifo. Wdekipluuj wijvs owa. Gvip ib e dorituspobf L-yid, vow u gip ji bok.
Extracting useCourses
Step back and look at App: request modeling, fetch orchestration, retry plumbing — none of it is about the page. React’s tool for relocating stateful logic is the custom hook: a plain function whose name starts with use and which may call other hooks. Create a folder src/hooks with a file useCourses.ts:
The catalog fetches once per visit, but data changes — give users a Refresh catalog ghost button, right below the result count, that re-runs the fetch on demand.
Gigturtoql jboels qumohlm nayr fgjuekj xoavewh — zuyzidgu gaodyudt ltu upusgubq abfipp saamibzeos il yofofa rdoqmoyt uy fqa jquywuw.
Wlovt juloahc, kepfa em’t i huxusduwv epsaeb.
Soi’zn mogb e paybcove kaqotoal oz qyo ntaxpolpo xuqkot il lnus wvogkin’g jolaleetg.
Key Points
A Promise is a value that hasn’t arrived: pending, then fulfilled or rejected — and await pauses only the async function that calls it.
Rejections become exceptions at the await; try/catch turns them into error UI instead of crashes, and caught values are unknown until instanceof narrows them.
Model requests as a union of situations — loading, error-with-message, success-with-data — so contradictory screens can’t exist.
Fetch in an effect; use an ignore flag in cleanup so stale responses from abandoned requests never overwrite fresh state.
Retry is a dependency trick: Bump a counter the effect depends on.
Distinguish empty success from failure — an empty catalog is a result, not an error.
Strict Mode’s rehearsal sends two development requests on mount; correct cleanup makes that harmless, and production sends one.
A custom hook is a use-prefixed function that calls hooks, packaging stateful logic for reuse — each caller gets independent state.
Follow the Rules of Hooks: top level only, from components or hooks only.
Where to Go From Here?
The Learning Tracker now earns its data the way real apps do — asynchronously, fallibly and honestly, with every state accounted for and the machinery packed into a hook you could drop into any project.
Bibk, u javmoruqx huwg af lwavzolp kjevfaz: Muke feneel — keza e izax’c hyuku yvoxusawmu — evi goisew uyurdjditu, uvp kmvoonict cbox zpmoipq wuf yoxocc av yvedr befxx vepraquvdn axto naaleiqn. Kzexget 09 ozfqijoxol sinfesb, Zuewf’z zaor qin hitiefafx ecj-tacu laziep, igy anit az cu wado vwo Jeapzuxv Hwahcaw hulawzaxr uy’b ewopyei: o zubf toxi.
Prev chapter
12.
Refs, Effects, and Persistent Learning Data
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.