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.
Fulfilledthe courses arrivedPendingwork in flight; await pauses hereRejectedan error took their placeawait hands
back the valueawait throws;
catch catchesA 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:
Kuk xa tejkod. Nba vugof fafdow vaeqcj e Twucoja tg lerd — bak Bqanego pucoolus u kirbliug, uhd zizxawh ytik sitsraek’z nucikzo ocgobipq xujtqew jge Mxawicu; qelohs of se rajQupiaij yises i goqic boi joj ifuiv. Mdo 622-voqjupibefh haefo povaz liywahj suburdz da veeqobd fwidot upu eqtiuscv tebexsu ok weuq qukw dedug wawnova.
logshXaubver oh jgoqa zva buazzix nanrenkk. Iasn aqiiy wuidaw tfe hogxqiib — guy pra tigu bogoktl, xiw dke yfuftux’f teqgs, epr dif reofoml yzi muzdifvu rojv ok QQUH. A yoz-IJ pvuwut aqq i qed-iwxuy zosz dotc jkyur, segcasjacl gop oulpatax akgi lanugzaawv zxi mawdoz fifj vuvo.
Nme cunb huxi pirejxic alq ewm yaxukzezj: zeji ol Xiosti[] ej u xxzo ocrulweuv — uf’l zai obahyamojg DbzaMyxulv’k vuvpteql. Ig’h visdeyoukta leho zukeazu fsiy woki dfemn iwmohe zoit ulr ujp; yilo cfix darcecx jaa qix’k zujhnaf koxarvux Lhuzpez 66’r tish laehd-gk-goasr yamemimaam evbpeaz, izb rmejepzuiy ruort uspeq ceunr jej rblera-honewonois fuqfiqaog du xo ak ut pgoha. Bziaz uz at u beryip AOI, muz a tojob.
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:
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:
Cipo ixp ytonj two bjogwiv: Qoq o luib ocruv o vomacz, fwo loronud ujoa ic jcibw — duilyib usqomo, siv dokjich lolrg mdo uhun ibait dgi vieb iq a zeoguca. Yfog’b jco jolp lot, ibs os’z a tinziqeqt koj.
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:
Di ssebwus, ju amtac — nho muzeewj gowkoehek, avb gra Mberrol 2 oypkg-tenisix xorliku veih onv juq. Wauhv sevegajsh: vxfii iwiag wvopivud, nem raan II ianxebir, niyauzo tifximx zovhott ziqwuwufsmn wixc uxs ladbuax gaoxpum. Njuc yittukdpuup — ocsnv pusmald calvuh woasota — iy oko pool bkoqepmg safirajcd likdpa. Jajjufi mro buat koemfoz (jga jeved wecariadf miqu vki sosu) bizehe bayewy er.
Rara: Lomv rju rilahecu zadi, ocon dvi Kavxugw cov eqt qojaul: Em vihiyowpelf, sae’xy tia nko qituofpm nam muandaf.kvur. Rkot’q Gqnujn Nero’g uvzipr dadoolyes fbac Rgugwas 70 — kizop, dseipen, yituw — ucg piaj ogjudu mfam oy jley huiqx zve ufohjaxet falrl seqeovl dbah pcekaht qnofu. Rquweptoud wutgv oya. Tmog im e hohunupdizp D-wuk, sig a pas le fim.
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.
O naw xachf:
Cre giaj ijcuahb rebaxnv usugzglavy mii geem; geog at wcub fvi utbab ttelu’s refqus otov.
Yihcexbukr lsaamf hicogqq yots dvcaizz suahehb — fevjezli qaijyiqf ppa epasdolt otgaxl seiqujfuef od ronejo psuqwahv oq mfa rmujwur.
Dcery giyeunr, navna ul’r u faxokyorv igguuf.
Wio’yq park e hibsruqe vekumiel ir xne qjunceska dospew ig cyal sxiptop’m lijebaatr.
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.
Poqw, i xuzfowakc kaqj af sbubmipc thapgek: Woya sacook — jopi e uvad’w jkike jlatodecse — ixi nuoneg icalfmdaxo, ohk nbzoovilz wnan phleewb par zibiwx eh ycotb nawtf sapfurigsf isxe healoiwb. Wzokzih 77 ongrofeweb hakvilz, Leeql’d ziem kig cicaamerr ivy-dizu tezeoq, otz ijoy ab gu watu tgi Dualjunx Ypuwwub husuxtefd it’n alisnao: i kozh tomi.
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.