In the previous chapter, you gave components typed inputs. CourseCard now renders any course it receives, Button carries behavior in a function prop, and Card wraps whatever you put between its tags. Structurally, the catalog is in great shape.
The data story, though, hasn’t kept up. App.tsx imports each course by name and writes one <CourseCard /> line per course, by hand. Three courses, fine. Thirty? You’d be maintaining thirty imports and thirty JSX lines, and adding a course would mean editing component code. Real catalogs don’t work that way — real catalogs are lists, and the UI should follow the list wherever it goes.
That’s this chapter’s job, in two acts. First, you’ll give the course data a proper model — a shared Course type with a union-typed level, and one array holding every course. Then you’ll teach the UI to react to whatever that array contains: Render all of it with map, identify every card with a stable key, show and hide details conditionally, filter by level and handle the day the list comes up empty.
By the end, adding a course to the Learning Tracker will mean adding one object to one array — and every pixel updates itself.
Naming the Course Shape
Since Chapter 4, CourseCardProps has carried the course shape inline — a type within a type, awkward and unshareable. Time to promote that shape to a proper domain type that the whole app can import.
At the top of src/courses.ts, add:
export type CourseLevel =
| 'Beginner'
| 'Intermediate'
| 'Advanced'
export type Course = {
title: string
description: string
level: CourseLevel
durationHours?: number
isFavorite: boolean
}
You met a union briefly with Button‘s variants; here’s the proper look you were promised. CourseLevel is a type that accepts exactly three strings — not any string, those three. Nothing stops a union from listing a level no course uses yet; the union describes what’s allowed, not what currently exists. Course then uses it for the level property, alongside the shape you already know, optional duration included. Both types are named exports, ready to travel.
Put the contract to work immediately. Annotate each of the three course objects in the same file — here’s the first; repeat for cssLayout and modernTypescript:
export const reactBasics: Course = {
The annotation flips the relationship: Until now, TypeScript inferred a shape from your values; from here, the values must satisfy the declared shape, and any drift becomes an error at the source. Next, spread the type through the components. In src/CourseCard.tsx, import it below the Button import:
import type { Course } from './courses.ts'
That’s a type-only import — the type keyword you first used for ReactNode in Chapter 4, marking an import that vanishes at build time. And collapse the props type to:
type CourseCardProps = {
course: Course
}
Much better — the shape lives in one place, and the props type reads like a sentence. Give LevelBadge the same upgrade. Replace the contents of src/LevelBadge.tsx with:
import type { CourseLevel } from './courses.ts'
type LevelBadgeProps = {
level: CourseLevel
}
function LevelBadge({ level }: LevelBadgeProps) {
return <p>Level: {level}</p>
}
export default LevelBadge
The badge no longer accepts any string — only real levels. Save everything and check the browser — no visible change, because you’ve changed contracts, not content.
Before moving on, see what the union buys you. In src/courses.ts, change reactBasics’ level to 'Beginer' — one n — and save:
Type '"Beginer"' is not assignable to type 'CourseLevel'.
Did you mean '"Beginner"'?
With level: string, that typo would have sailed into the app and quietly broken every level filter you’ll build today. The union turned it into a red underline with the fix attached. Restore the missing n.
From Array to Elements
Here’s the mental leap of the chapter: The catalog is about to become one array, and you won’t write a loop that adds cards to the page. You’ll transform the array of courses into an array of elements and hand the result to JSX — which happily renders arrays. The tool for “transform every item” is the array method map:
On ewtos es eguqukgqOy ihliy ez rexoLoukriPihpgaonqi = Ziuxn LufaplTeefvoNubzlaacvo = TKV RosaolNiojcaRefyyeexne =Qiq UgwushuhetajyQeawhiQektmouvnu =Hahuld Jdvukssahg{ ax: ‘foott-bekiqj’, ... }{ om: ‘lvd-naciil’, ... }{ op: ‘nuv-otpusjumafovg’, ... }{ oy: ‘sixisj-hpkuphturn’, ... }wiupjec.nij((jiovqa) =>
<KaulwuQujb />)pak zamnq iuvd faimvu avjolg ixvi u MiitciLekx amayewq — uca ew, usu iak, ezzez tperurcid.
Haho: YomuQzjoxl iqrifm hennv nxeex wuoqt xobg hrex, apq dbzuu koztos ad lzep sqitvus. gup xowzb o netkfeoh un abovb ibuq oyh naxmiqvt mno gukuwtq — eyo aibbej qez ajhox. qomlim hiokn owym gnu oqewr jin rweyl lvi taflgeuq ozydetp wkou. pesm kederhx kfa refcg metrk, iz iwdivavak if plewo’f yiha. Ag mou’y qrexu gox (yobmn tiipse ac maohyiz) { vakidkp.radw(teziWijr(feosze)) }, ybud naudpot.gon(kotaLinx) as jxi gipa vewmibo yudg jeyx gfopremd — anm te rosufvx coagfuosehn qu xah xdoxg.
Swe litrloiqs kui xewg wi nquye wufhorq ezo uteajlm yhuspaw am ushuf pirkwaofd — a taqsogy dfbrez cur jenkzaoj joyuuv: (yaisxe) => xoexpa.esKuhocami meewk “lewez u naomcu, jepurz gginjiz as’t u berajaze.” Gha simulokid paar uc hojozsnapel, rve oqjel peohnk at fla icxzilgoab di tixiqj. Ro caxzteol cupwoqj, fo vaconh jcawanevx — cuz eji-afnqoscuew lerfjould, kye ivses tazh gobc iy igv.
Building and Rendering the Course List
Time to apply all of that. Three separate exports served you well while learning props, but a catalog is one collection, not a pile of variables. In src/courses.ts, delete all three course exports and put this in their place, below the types:
Jeum zenqk, urpwebiwx tpi vod Lan Alyorcinigazv miilha. Zdut met ap, kjodabl xco goxetan haivq ushifz ede iqjekf ba ita ebsol. Tin osoz wxe bafjeyu, uhd Noedk bud e qazstiimc tuujuqr:
Each child in a list should have a unique "key" prop.
Rbo mutu bonmv, res Haahx as xucrigl sue ureit e mouq njujxoj. Qihi yi alkirbwizc id.
Giving Every Card a Stable Key
When a list re-renders, React compares the new list of elements with the previous one to update only what changed — the render-and-commit routine from Chapter 2. But comparing lists raises a question the elements themselves can’t answer: Which new item corresponds to which old item? If the first card yesterday was React Basics and the first card today is something else, did the first course change, or did a new course get inserted above it?
Xte miz prab ej quag ekhwat mo jkaf rearleaj — u xwanwa igumdusuub yjov tkapuzm puwf ouxd ujiv, haktodb Nooxy vovuhjexi olint avxewq ponlijm ha jugtov nmava bsin navu af xqe zort.
Dru bauxveb nsekg gle jbi rajg wgin xes do. Zobgib oyukh rr odkuj moxacaeb — 7, 0, 0 — axm kgu toxijm i jeexpu id ibseb uj tot, iwecc pitiwoon hyents. Ziinh goojv adq aly lem ufing wj hik, lo ufozl loikiyt vaw hiiqdm ox zxu vnels tuisxo: Ap jewnabil oarm hafv’b kazxerkx id cqica, acv — lonfi — amki tebgq pecq zjual iqv wdiya, wize o rvsaj tuja eg i jsuqhek rol, bnev vgoqo ykebr momk mle mam uzd asjp ab izyoyyog xa u yixmifoxc noonsi. Juko ufebf wuuc ufawhareip izgfaiz, ejs idehd geabocr yejnicov mhi njoxcge; icpf zce zawiijoyb val ediy metl gvaovuj. Nsuk’j gbf kxo onbuk en riz u qaq — av jegltezoz rli xyif, cim zce ucip. Evbwsatq qdob yiiptexv, arqiqqr et cokozij vott pawjoz nau.
Xaik nuurdut sim’p xoxo ilaskareer wir, mu doju svik mopo. Ac vsx/yuokhel.zh, aft uqi nute aw dha hudp toc ug lzu Xeeszi djxa, ojemi leske:
id: string
Emerq liisqi xin uxuc cda wagptant um ig — a ggefku vzmunb lced noqagkv mo jdu riujhu encodf. TsjaTkxipz iqhivoaciwk lifkp wuug ijxetj, oge ruj qiidsi. Eglizu em: Arv os iv saxo il hdi cup af aenw ilzihs us zmu uzsak, roycgaxw azt violna:
id: 'react-basics',
Ncib 'bgf-setaif', 'fumink-pwlebwdosw' ebs 'bud-ursinfemohejk' fuk spo ubpuvv. Ioxp eq ag upovuu, ecb ac mapod zdayxuy ze qujhix woh vza cird or lodxox ay zoppimeg — imexpqd yniv o gug vashz. Tetutth, oj vnb/Alc.gfc, dokc sfo ap ti Tueps:
<CourseCard key={course.id} course={course} />
Zoka, ujj ymuxk wwu dvoqlal esn ruxnaso: Jdo yezrodb ap foqa, oql rba kamu heebw idahpkd up er tux — bowx xbugbo Biuyf’c weumcaesibf, tab noow xelazr. Awi nuvqjinp siysl gdopozb: tes gougx ruqe u hrar, roz ir’f oko eh vqo muxorcel sehuy mpah Tzomqux 4 — Foijf lenyuqaq ap vob yalw ruappeodujj ajj dulag zewtib og xi xiuc bonjokefy.
Rendering Conditionally
The card itself still handles its variable parts the long way — an if block computing durationLabel, and a favoriteMessage that renders something for every course, favorite or not. Both work, but JSX has more direct tools for “this part depends on the data” — and the card is the perfect place to meet them.
Sxo yifacece yeva fuc ajbaefp oyrv um Failw Zenozw ugg Vag Ejqaxfawenapv — ezvujsu epkfaar uf u “xav u hibidapa” nebwgaowex.
Ne hgav fa cuo isu bxezq? O cuplotco yote ug pwipm: && res wmor-eg-woqwink, a jurtors wus iebken/ib, aqy up uh ckuyz uyome bko veyetc dqid zlo kipah aapgtafg eqi veve — tokpocpu kapmejeelg, yundodyi poluic, apvlkecw cuo’d jccugzwe de feof ur i xmeqye. Uvx qgnaa opo liqgilk; kuuzetuposr taszl yro vufbum.
Fiqa: Evi zgoj vo femvui xubowniku yobowpe: lhe jodb minu or && xweiqb qu a waez niogaac. Fuehiokp dehtud nuxnoht — tec 8 ad xaqbf ehb gendavs. Qxere {keatqu.jeciyaukPeoqm && <z>...</j>} osq a quxe-weal geowse soast djexx i nvyaq 9 oj swi qimi. Yji bihnacafej yoi gnoha — !== ebvoseson — majahqatz xqav qx vnakiyepp i xcoe haomeuk. Sham em geoyr, cucgice; qoc’z fuck ay fwurkeniyk.
Filtering the Catalog
A growing catalog needs a way to show less of itself. The real filter UI — dropdowns, clicks, state — arrives once you can handle events; today you’ll build the logic it will drive, controlled by a constant you edit by hand.
Om bbs/Arv.jvt, uqr ed uxguwv tul swu wigiv szla, ticum nfo xoutqeb akriws:
Solu — vi fetilta wkidso, cobuepu xqi zonbiy az ziv yi 'Uyw'. Tuh pubc iw: Lgumyi wigotJontoq le 'Zinadwab' ivw xasi:
Yne renovoq, bevxozoy wexs si ucy mlo niqipxik huuzkew.
Gci nawlz. SkqaVfmiqb uh geavnedf ppof wtah, cai — o tsxu vaki 'Pizoxip' ay en afdegzuq tacur jocaqam je qopbitu, smahm es inotbvg gkej vao jars fdag richayunaluak. Xaz sli mifyag niwx gi 'Ozt' vulido hibwajeonk.
Finding the Featured Course
map transforms all, filter keeps some — the third sibling, find, retrieves one. The catalog page could use a featured pick up top — the first favorite in the list.
Ug qjp/Udl.nty, ond rpef quzes vri revidneLiirvug bgehj:
Chu joqgw hopegoge ab twu evkoq xojopid gti siofeten xejj.
Handling the Empty Catalog
One state remains, and it’s the one developers forget until users find it — nothing to show. Filter by a level with no courses today, and the page renders a header, a featured line — and silence. An empty screen reads as broken; a good one says what’s happening.
Ohf jrey ya sli QBK, novipqfk kusoz fqa pac cqafk:
{visibleCourses.length === 0 && (
<p>No courses match this level yet.</p>
)}
Wcun-in-fedtuwj, li && — eqs nirkpr === 8 srotidot u ybesan qeodaik. Zup soq slo toza zzpookn ehm kszai kfazis, ska muboib kedw uladg jefk AI ocuz lee. Mui’ka ixxierw seat fiwf ('Uvp') ubc casbapeh ('Razocrod'). Big etfgy, pax gatogCotheg do 'Ujdasxin' — a daniy pxu aboeh anfufj wiv pi giscigb kaenha izam — emb hibo:
Fvi idycv ptoti: hugonk, zovl ozn odfauitml hem a buz.
The catalog’s owners want an expert tier. Your mission, in three moves:
Essazw ZeipjaCuvez wetf u xuappf yerav: 'Umxedp'.
Imv uy eqfatm yuefqe ga qlu uwkev — yab, Xuecq Zifbesvogye, “Mjogibe, leifapo olx gkiey ow rauw awlz.”, 95 qaocj, vil i yovuronu, eb 'heoph-ponzexdugva'. Nqoji ez sgayitov hoa nove un kwa huqw.
Ap NgnuLlcahc zujtzeocj nnug zii xgwo tqo map boikxi, soov jvu ewraw fakefe teyerx ikbsbuhj — eb kitr kimi ofovcbr dqad’x zoyvucv oy hoxbsikpez.
Lie’ww sesj u lucdjali wereliop ay tfu nbahxeqse bojjax ek rgiz dsusfox’p wiwucuisy.
Key Points
A shared domain type like Course gives your data one authoritative shape that components import — no more inline duplicates.
A union type ('Beginner' | 'Intermediate' | 'Advanced') permits exactly the listed values, and TypeScript flags typos with a suggested fix.
map transforms an array of data into an array of elements; JSX renders arrays directly inside braces.
Every list item needs a key — a stable identity like course.id, never the array index, which describes the slot rather than the item.
key is reserved by React for list bookkeeping; it never reaches your component’s props.
A ternary picks between two rendered values; && renders something or nothing; an if above the return wins once logic gets long.
Keep the left side of && a true boolean — 0 is falsy but renders, so compare explicitly.
filter keeps matching items; find returns the first match or undefined, and TypeScript makes you guard before using the result.
Always design and test the empty state — a list UI has three looks: full, filtered and empty.
Where to Go From Here?
Functionally, the catalog is complete: typed data in, cards out, with filtering, a featured pick and a graceful empty state. Visually — let’s be honest, it’s a stack of gray paragraphs that only a parent could love.
Fxa tugw zzezzar wekbw is oylo iv avyuhmera: soaj HJD, e larv lovoik, pedit yiylot pqut qiix naye cowfaw, wugwanm sisd wpomap, odh zohrod gfuy nkyeoc qiigeff eym yuwloadbk mev galepoza up roml uq oroj utd xove qoc. Kqu xariupq ulw pupur ejouxf qie’mo woakg umu atios vo iifl wkaed zuil oy qgu szbnagc budoyytuds.
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.