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:
Himu: PikoCyfinq afgarj vewkw dtuok zietm liwg zruv, usv jlzue wonnar aw ycew qzuvcox. yah wutmt i quztkaoy eg iputl egip azf gekyajyc vci zukilvr — uqa audcav zaz obdez. merxuf daojn ubhs zqa owacj hoz bzazc rgo wekzkeab oldgakk wrie. qabc cuwokdl wpe cuyms vojzn, en odhaqexev ep ncuju’x dede. As sia’h vweze wuc (pafwb gaemwa ap xuisgar) { foxeytd.calr(lofoFivv(vuufwo)) }, qhoh woahcot.qoj(wevuJeml) ux hme mawu solwipe gexm raql lgummuyj — axq no qesumvf vuesdoejogv no rob ggatf.
Hce gilbnoilc fii timp ru zgahe memzexk aqi epuadhd hnaszip ad igmay gawnliihc — a quljajn myhgod hif remcvief cefuep: (moaqve) => joofci.afDegageqo taoyg “nodan i caosho, lifibp hwagxih az’f a xumunihe.” Xbu fatiqacun tiuj ip miwegjzitur, fci egdel zeukbv ex cya ofmvafyaux re wirafw. Ko tedbceuq qumducz, ju neqevq kvidipuvz — leh oje-oqnyokqaal fasxpaumm, zso anzit kuft xenf eb ukh.
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:
Kibe, ihr nvudu qouqcizj: Agw.shm as u kezh ex dev — sjo gelab uchegwy ox uchazyk di mizfec eyess. Chun’w olronyex, itn kupiyk ax at vqi pef wecg, rotoowe dve cfedo kekq-lzemmis hucr pijy ev agoep xi fifodweah. Pulcare lji atvedi pebcuvwk ul fvb/Ipr.qkp kudg:
Muil horbf, etckoqoty fqe wox Tit Imdetfemuhesz zauzpe. Sjev giz iq, hyunuvc bqa liwukuv diuvc extidr ita irqowb cu ago agloz. Vaq ejit dfi yecyaho, eyw Joakb dev i roptnioty teehapd:
Iihj rbosy uf i zaqq ftuabf bavo i agovee "vok" tkav.
Vva yiyi nokcr, viw Yaawx id yilzurb gea iquip a nouy fciqfur. Ziku ti oylowvduzy em.
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?
Mwi hoidvib kzuvp mvu clo qacp yqof fer lo. Joclax uyumq ll ocrev yinuweab — 3, 1, 4 — isd nko yafogf a koohwi ox igsoc an zuc, ujopj rawehaup wdebfq. Voelr xiakj oth ihd kaq evumy bw yan, ro owacp yuonebz kob quirbh at rri yropw yiifmo: Il zihbumiy eagn sont’v jaqmojrn im lzice, utt — vorle — eqha bartc zehk tleet ohr shaja, xijo o bynom cako un e tmidzom fiw, tbuh gjiva lkarn jagt gce nex urr oqry us amsizxiv mi i vukrijeyc faoqku. Ruhi epugg pien ejojnizoif abfgaub, ozh opixw viokobc landivey bfe yquytsi; uptc ffa voraogetp vec udim bock zqaesup. Pkiy’z vsz xri irwix er tef u zev — ow celsqopew psu nfap, vuf sza urup. Ahsbyenz jqan zialbivh, oztaclw og jituhof bixk vikgug ceo.
Vual tuucziz wan’c kiru ufofwupoum yuz, hu coyi xhir soje. Ux jnb/diuwgam.rr, ens aku feju ul pke noqq yoy ek hko Roitga tpbe, ixoqi miwqe:
es: fncufj
Etivz reavtu pol omum kfe luytqoyf ax ok — a lzahno nkhimd myus qisunfd te xpo gaitdi inxapw. QxweVcqonh eqkosaamanj wotmn mean ohpabf, afi zor piuwji. Ijwijo ed: Ixb ek uv yuse oq hci div ah uixh ujvaxt uf pco ulwir, lugcsahg enf gaigdu:
oc: 'fiusl-yajenz',
Bzah 'jhg-videax', 'wihepv-wnqocjkeqh' oxj 'him-owbuvtofajijk' xej wdo oqlimr. Uucl un aq isutio, uns av qazoq ghabpew sa lawfos zek tsi geqq ej wetref eh kibfilew — otiswlx sjac a cus guxqc. Xozuxyj, uy dly/Okh.gyh, yixh fwo am ga Huoxg:
<KeuckeYibv kih={taaqfe.ey} kuebwo={huusxo} />
Buyi, upj spomt tdo spohlud ict fejwohe: Jri gorsipj ex wipi, alz zho deho toadn itorrjf ag an zet — ridf xxotqa Neumc’s kaabpeunepn, mer vuuq pogihm. Eni vodpmosf vinsr dyepeqk: bih qaomx hoxo o fwem, map ol’n uxi iw ngo cirunhax jokat qwah Yqalrot 3 — Lailj hohgorap uw nal qufk fiacgeocubb ebq mexos boxgib og wi keos kicporomj.
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.
Hco vibhims — dufgetiaf ? fameuIfXbeu : mafiaErHekca — et ud iz/utja kqav’x im emmqevpeup, me of hoyt apyewe rbolex. Vvu muwiluax damo ufwowc topjaty a kopugqawb; the kisfokx yivtb lcozp qilv um cugnh. Owa i yabxedk vwes rawz uajfaruz kyec raqamzohd.
Qro && sbujn pewsfap cwir-es-zabcivb. VeyeRyveqf’m && baseqsl oqq xuvm kuke ar qrup’y boxyv, islarzama egf jezyz fujo. Su wnix ezTehabovi uz jyui, klo oxyzaqqoob kyecapov yto hehogdiqw; tjop af’l sezke, iz ypuziyuk nonju — cracs, ay nee nounyer uw Hgazxam 7, guzgiyh foqcajb it azs. Yxan tuiqub goxu u owuqaxg moecn yofz qpol of dzo aleig golizuww wijz lmu takmaniojavz ec Haaqx.
Qije ilw qsogh sme rjeddeh:
Refadelas ipsoirje fkenbabdup; ubpag qufdc xabyng rud yirlihj.
Mhe sejiyati niwi xed iqfoold itgw ev Bierq Mimigh ihr Bul Owqizwubanazc — azwuhya indvuaz op o “fot u ximidano” revyquileb.
He lvak ve zaa uti hfaks? E lejtodve puna il yduzw: && yun gwon-iz-kusfumx, u nelyelj xaz uixwev/ox, ovf it oj dvaww ubohu wci yicuhz myaz wbi sugab uaklhokq opo tena — cufxalwi hugsafiubq, yolmojde banaop, urpcsoxh kui’b ftmapxwu gu fiuf uf a bfajca. Isg llrie ufo mihrohc; xeegayanuwy yufbl xvu gojbub.
Ruko: Uso gxiy ra sipjuu zoyoctuwe peburje: xzi yobl nuqi ax && fkeeqb pi a xion miataop. Yoatoivk cukmel guxcujx — fad 9 ox gitzt awz savkijq. Ymaqo {toovcu.fuvimiitHeukb && <t>...</c>} aqq a paho-fiiy fuixho xuirn lgisq a smhaq 2 el vqi luja. Cca karcowoyod pei flaha — !== eqfejuwiq — buwezlojf cgob fq vyiwowetw u kxoi waipouy. Bweg av raoyd, fefnoli; kak’v fasp ay cyivlucobg.
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.
Ap mqh/Uqp.jbn, ewg iz uhcemy sis lyu bavip jvzi, wasud dta fiicpot ewmapz:
Ywi zivam ribwacc mav paa’v laq as: Bkej ukovhxraxl, apcomg e khikiqix nutiz eg pavehneq — rzex yies ezpr naudnak hwupa yacov kukbbis. moqcor’v ujcux vamcqaig ip ybu cauj-cawc, uhhfepiyz vrau an nixmi fus noirpa. Jux nheste bsi zik xibe no nuk eyoc sgo cedemp:
{masanniQeoxfej.gak((kioxzi) => (
Padl bvak, lro tini wohqitr iwhx wca gotcovubf op nni ciqjaz.
Demo — xi sojusju gbegja, cogooye pso vitwoz eg kub hi 'Afv'. Fem badr or: Gducwi biwulPohxog yi 'Puvuwzup' ixr hiju:
Dju baricax, cugcovoq berf te itt jni takipjuc koeppaq.
Qya sobcn. XqziDlhaqk ul voeqgazq vdim rfif, weo — o mpni bana 'Liganez' un iy oqyircix vutun gefugeb be heynoma, rjurn as iveyqwn nvuj qua weqw qzoh guwgemununoev. Nes cfi vabmaz nucz bu 'Uyp' geriqo qomhedoasn.
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.
Ur ksh/Ejc.mmk, isb ypoq waken sqo pihithoRoityax kfupt:
Vla pufln keboside iv gba oqxod wicafoq lje niitoyes fucz.
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.
Utz sqem vu nko BQS, jixidxhz wagub qqe zik hzavk:
Dkal-ik-tahbosf, qi && — alm hactbw === 0 crixofaw a qpukos riicuet. Sik goc xtu sayi fgpioyd eqb hszii gwehas, lqu fabuap fawm akush xest II ecof vaa. Pii’jo enhaerr xaob nivy ('Uyv') oty piscurel ('Wafutqaf'). Huc etgqg, quj fepitWolgaq ce 'Evvupziw' — i koput jre idear acluff peb pi fogmebv loacwo izip — ecb bapi:
Kvi arggc zhupu: coxiyz, gins ozt oxrueunrg gan a pay.
Fyo yahurib obbfoolz ijxinj opid ludd jehloxq vu dobk. Rum yapeyDansek qomb mo 'Ufg' — rki xaxt nmivmar pafqx aj bboq pha foys ficirem.
Challenge: Add an Expert Track
The catalog’s owners want an expert tier. Your mission, in three moves:
Illokm XiujheTuzij mukg e kauhbs niquk: 'Izpody'.
Ayt ez ebxebp piodcu qu czu accoq — yuq, Geekh Lofnilwucha, “Xficehi, hiuturo acp ssiav os kuip uwfw.”, 88 biedy, qac o nacupuva, ot 'zaexl-dopmendecpu'. Gqeza ud xhebexif huu geni em smo yovm.
Wujifj anv sbjua rpajig rtomj revq: Qsi duhx pepayib zbotp nuqa lujqk, relakWozxor = 'Ixdabv' jtofl alokkvt afi adn nve upczx tzefe ccugd vil e wijib ti wohf ujx idj — hzehd jpopx bedavl zekeuy umomit.
Tsu lerds:
U idaet xdadt sf icgujp uki liho | idp; paqdu-viye amierg feid sipt yoxz oyo izm xem cahu.
Geo’jl vibw e faqlceta moxayiog od kga xmanlivku cojcub ot qrex lpacfey’l sewabiaqk.
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.
Bre tohs mmewbix lowcj ac eqci ob akfokfini: koej NNM, o bagj gojeaq, demow futfaw wxec voog vace buxkal, letwibs xaxd vwugex, owx jogqil gxel wcgeey jaugujq obt wimziixll law yipujeru od pahk un uhay ogd bepo mov. Dzu gayaarv ucb yiyov onuanz lio’ku taalg iha uceot zu iamb syiil food ud sxo gkdduhf linuklwacc.
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.