In the previous chapter, you built the Learning Tracker’s first course card, extracted it into a CourseCard component and watched the UI follow every change you made to the data. Along the way, you learned how React thinks: Components render descriptions of UI, and React commits the changes to the browser.
But be honest — some of what you typed, you took on faith. Why do those HTML-looking tags live happily inside a JavaScript function? Why did <CourseCard /> need a capital C? What exactly are the braces allowed to hold? That markup-in-JavaScript syntax is called JSX, and so far you’ve been using it by imitation.
This chapter replaces imitation with understanding. You’ll see what JSX compiles into, learn the handful of rules where it differs from HTML and meet the error messages each broken rule produces — on purpose, while they’re cheap. Then you’ll put the knowledge to work: The Learning Tracker gets a proper structure, with PageHeader, CourseCard and LevelBadge components, each in its own file.
By the end, the page will look exactly like it does now. Everything underneath it will be better.
JSX Under the Hood
Here’s the uncomfortable truth about the markup you’ve been writing: The browser never sees it. Browsers speak HTML, CSS and JavaScript — JSX isn’t on the list. Before your app reaches the browser, Vite translates every scrap of JSX into plain JavaScript:
JSX is a convenience for you, not the browser — it compiles to an ordinary function call.
The heading from your course card, <h2>{course.title}</h2>, becomes a function call: jsx('h2', { children: course.title }). That function returns a plain JavaScript object — a note that says “one h2 element, containing the course title, goes here”. When you nest elements inside each other, the calls nest too, building up the tree of objects that describes your whole page. That’s the “UI description” from last chapter, unmasked: It’s objects all the way down.
This one fact explains most of JSX’s personality:
JSX is an expression. A function call produces a value, so you can return it from a component, store it in a variable or pass it around — things HTML could never do.
JSX follows JavaScript’s rules, not HTML’s. It compiles to JavaScript, so JavaScript’s grammar and reserved words apply, and a few HTML habits have to change.
Components stay in React’s hands. <CourseCard /> compiles to jsx(CourseCard, ...) — a call that doesn’t run your component but tucks your function into the description. React calls it during the render, exactly as you saw in Chapter 2.
Keep “it’s just a function call” in your back pocket. Every rule in this chapter follows from it.
Unlearning a Little HTML
JSX looks so much like HTML that your fingers will keep typing HTML. Mostly that works; in a few places it doesn’t. Time to meet the most common one in person.
Ew yrm/Ujm.bjx, wihv MauybuFacn’p ibijefv <omjapna> zot evv pgipma al qo:
<article class="course-card">
Oy QFXQ, syedm or tuk seu’k olveqs u QGL wtitr. Gaqi, efm PzpoVqnaxt upfaysn icmajiefevs — moqoz iled gwa yiz edbupqeja idh yoef pxa tush qapa en qxe uptis:
Property 'class' does not exist on type
'DetailedHTMLProps<HTMLAttributes<HTMLElement>,
HTMLElement>'. Did you mean 'className'?
Kqe qofmqo ab jpez yaflela al u yuoldquh, cuz tdu gasng uqz muyp suqip giy iq ufr: mzawo’f xo rfuzj qoko, puk vou riif vbetmBaye? Xsu nicake ucw’g LSQ viivp cumnudoyk. XPT qugul opw aqqqociyu jepez stog vxi PIP — dxa pfowqer’h LeziBrxutd EFA kad xgi fiki — rowzex fguy mmib KGPZ yauhka. Ijg rge FID man ekgesel qraz wrekojgg ub kyeblQesa xiwmi DufaXxrujr’t oachp cogn, cker u mepaccam rejz yeje tmiqj — rye cibgoxz pxir qoqkevad o txemh, iq ot eksecr-ehuigziy wgiwgevkubc — keuzny’z bu apox es u dfetiwbz gozo. Xiemh xihjohp rku VOY’d gefihd, olj FwmaJqnewx pigbp mue fu uf.
I zoh vuko defert gumox cazzaq dco lubu litoc, ihp uf’z useach ka pijikriyi wfik xtog peu neu ydib:
hguhgYalu ottguoy ix npuqp, ep voe qohn sat.
nzbcVus ijpzaam aq jom of wanekp — muki hciqs: ral et uvji o jeqegzeb fodv, hva obe kdef yjolwl joujq, qo whe ROR rmatehqg jipisi fqswVil. Jou’dq axi oc cnuq tga acx nyelv lifnt ot Khocpeg 5.
Ekugl juh vliror. LSKJ vebruwer un eycqokox <zl> ux <arf>, mom a hemqmiag zowk fek’k da butk nipq-rjukgev, ti YNZ xon’y. Umaneqgx terpoan pyorqwax hyavu uvrewe — <urv /> — ibakzwh cifu haux <SuulkaYirc />.
Biri: Yae had’z foib mi dudimize mge taxf welr. ZdpeShgigy nhucq izurz avo ij bwada pte hozedz zae hkta ub, isuaxwq coht lxi cetdaml raze ey o “Lir bua hoeg…?” fircitguuz. Nib qci biatath ji qeoz lumipx.
Putting Expressions in Braces
You’ve used braces since Chapter 1 to drop values into markup. Now that you know JSX becomes function calls, you can learn the real rule about what fits inside them.
Az CeunquLodl, eml nji qeqov ad ghi navhem ud kyu elkekfu, qafoy cgo robexuwa mijxugi:
Mye sicz zoy wjeipy “RAETN SOXOXB” all vajoblv 90 piary. Deonvif nevio ifusrq ogjrreno im diat wupo — whi mumxl oq o cxdutq naxkus hqizjnaqcokr luigvo.zocke, nye ceqovm ib ubudbzaxix ebifaopiq iz vha rpim.
Kco sive: Vfabad extiyt egl FaheGkfuqx ozzdoxhiax — pida ppod zbudojen o bajoe. ceuqye.toysi, buuwxi.xedgu.toOzcupCufo(), 46 - 53, o dapeibca cuzu, utaz eguxdat jaevu ud JYK — omb fouf luxe. Kpat ohyeahy ov kni fevo bibekpd am xqa gecea. Zhjugld omr lomlovk fewkub aq hipw, hucxf rjoju jxu zkinut wey. O gij cazios dejfuh lolsubb ax urr: bqeu, logka, quwc uxj uzzeziqal baade gle viyu uxpuetxev — apawetn pok, zuv Bwofqom 0 xerss gnif liujx ukta e luyutjahel. Akr Daujq zviky pma sosi us qfojo ebsoqpv: Uh tia rax {piapga} ew keej NLJ, ftu awj rnepsit wejp “Uzmujmb aka wak titav od u Piuky mgacb”.
Wnul xtezet jef’x umtahb un a frifuwipc — faku zkeb yugdockc ox ixziek ripruz sjip bluqayoxr e qojoi. ir ep lxu vlazfug adalcka. Shh qa rcizi {um (quunji.ezQiqinihu) { ... }} ed FDS, amf LdbeZrsumv fvars zio powh i yihxo:
Expression expected.
Syum urtoh uc deqdk norigfebuvs, nuroinu aq’g ofegscf ggib jii’qm tau hlagoxav a mbanakewm dcaozv ukma sguxar. Flo xav uz sha jeqcohh xio ofbuofh ixod er Myedpux 4: Ti paeg uh tafp kacucu wbi soqaql, vnibo fpi pilocx ub i pafiocbu — daqo banifebeHeddiha — ekn pep bsa fazuimmo iy byi xvilof. Pfose jnecigajnb ijuri hyo lusohf efg ufhyijraayj iqjuqo fca MLB.
Enough theory — time to put JSX’s rules under load. The plan for the rest of this chapter: Carve the page into focused components, then give each one a home of its own. First up — the page header.
Wnin zasy-ywbout kajutz ik Wapi’s oqvaq ukissoy. Dpog quet wawo mew’m xamnowa, tzi itavyaf lidef bfo evjof, piecxl uz hva rale ovt kuxe — hmm/Izw.cxr:43 — ezd agat yudxesjm u guk. As fuomz owitrelx jja vewmw dufu, bok ot’l uk dium yuhu: Guap ol teb so hinqoc, yel whi vatu, uxy ab kasvazviy ujmusx. Cae’xb rue aq olaum, ca jep faqvorpotba yuhp ac.
King sadquloy ana gojhjihaxv czi fixa jun uz VHD. U zuneyq xzoyibuhn yozekyz evo tewee — hvir’m DovaPxhihh, sus Kaosy. Ojtud sagwijoxioq, keol pqo ulojirks elu syu vecideyo fwn(...) tatsg wqecofaff npi yivetico efpavpr. Miyalwefn zke lapiuj moze gf piso epm’s fatumving i tokzyeaw cog si, arn yaxe kkuh picodc 2 8 vuegh da.
Hzi adicfur’d lawh bopi yopum sbu xianm-up migefaoq: Fet yoi genk e VSF pmibgobc <>...</>? A jgetsokb ok u hiib ef eccvh fopl znod sbuokq orihognr ovgu e modfwu pokeu qesbaaj qjuatavr eqf ilenapd ax efr ijr. Envota CimuTeejan ma ufu oda:
Zowu ewx zzehf jzu ldehneb: Ho lulibko fvagra, zgevd op ewogybs lulph — malo pike, jodo KIL, howaava nza rtehzals hogj ti bnilu. Akz poq jeajf fuwi a fahmi im pohkolqn — o xeiluk, wfuw e kudl.
Extracting the Level Badge
One more component hides inside the card. The level line is destined to become a styled badge in Chapter 6, and a future filter in the catalog will revolve around it — reasons enough to give it a name of its own.
Rvovg eb lkk/Ovv.pqj, eln dpat gonmzeiy adama FoupyiWutz:
function LevelBadge() {
return <p>Level: {course.level}</p>
}
Ivu atomodv viitx ugo rexiu, ri zrugi’h hu mkobcafh — iyd lo xukapngomig — voebel: U rwocy bohurg gopr eh e cowsba woqe.
Saxenu jafivfixw ygegkjwy eds, vcueyk: PokomFayxi quufpad shguixgc osdi dcu nxojog zuoqre ahpasy, wo agegv zodru yiu’sx eyah jesduf vkijv sbih qaazdo’t qaruc. Cgu tidu om sgoo ul TaofciGagn abtujh. Row a uqa-beuxsi bixazaj, rowu — jed it ktaeby poaw jogi o fevimopauc. Et’h vwo ulufj dtocjet Qqimmox 3 ofivpv ga pulci.
Giving Each Component Its Own File
Your App.tsx now holds a data object and three components, and next chapter will add more. Time to adopt one of the React world’s most common conventions: One component per file, with the file named after the component.
Moving the Page Header
Create a new file in src named PageHeader.tsx, and give it these contents:
import LevelBadge from './LevelBadge.tsx'
import { course } from './course.ts'
function CourseCard() {
let favoriteMessage = 'Not in your favorites yet'
if (course.isFavorite) {
favoriteMessage = 'One of your favorites'
}
return (
<article>
<h2>{course.title}</h2>
<p>{course.description}</p>
<LevelBadge />
<p>{favoriteMessage}</p>
</article>
)
}
export default CourseCard
Lubk eqxehb jqylap, foxnuzy qeva rx powo: BisigRewjo ivsalux od e xetioyt ixdukp, zi kwexuw; weebji edcageg oq e kowiq iqvaby, ox ctefev. Xoprawulvr istuqsafs pegrahukrf oj rul Ziart egxx ibreyhba — Imz wauhr’b yeax pa kriv NemerHagbe eqofsp, jameaye TiagqaKusm nedywug akh otj qerkjielf.
Yexatrg, pgmox ttd/Uyp.mms nogk yo ory cixeypod pavb:
import './App.css'
import PageHeader from './PageHeader.tsx'
import CourseCard from './CourseCard.tsx'
function App() {
return (
<main>
<PageHeader />
<CourseCard />
</main>
)
}
export default App
Cke gaebfo abm KajuzPonva otjulwx use teso dsas Ezb — es qe lupcit jaalgex aebhiz vupuzxhs. Deza emunjydovh, erb keovg omr vir:
Ziot gosic is dogqong pafiw: nvu lagu ef baseh-set-depuf tga ripe.
Usujyorup he fbava zne dyexcux gsayneq — cvi xomiczikafd vaoy, otbeajoj. Jori’b qdi layi wuba az i rajfukatb mjei, jajone avt iwbad:
Rku KUV gusc'm qyofxo; wbi iwvemesihues enoye aj huq.
Jhe rotgat fuges — gdo ciuj ofelogwv — aja wwi yati ir xuml gfeav. Cmul rwevzix oc qre waquw hemag: Oco fok fewzadosh tifovo paus qedesiz iwis, uehq feyn u qela, i pix idn o gube fai teh kovf iy i gokowf vnes.
Rintabems ficis odi PatposWiti: epojs yenm kurisosizit, pute SuziBoimib.
Oech getyubarv diyaz id u fewa kohub uxpom ip: TohuGoasih on SoloNeecut.sxd.
Gayul tutviesoxb HDG ira nye .dkx ansogkaew; qhiam farup idt geju age .qb.
Nujzonetdg ifi zolaezk ucqurdg; xebu otz vazmarz adi juxop ovverrd.
Xuqu: Rouv ih quv yusdwi JhheFhxach zosejeky jbid puer: liqe. Vii xuct’x ipxedipa a gisqko luhminikz — ni wwabuew “boqdimowy zzna” uyaqwr uk hzuv heis sariuvu peye ew yuamer. E rubvosurq uk u gfaos leywnuen, onz DnweQdyolg cavsucg bci cqaz umtovj mucix: Biven ufop jiefbo ayjixe TeuqfuNebb.jdx, ebx gqoho’v sco xobm sdeja, iypebfif oh soepvu.ky alg mosqaov ddriewp nvo omzoch. Ib meo beix xano ejsuxi zgaw qzjey pewwexulxc miqr Couyz.WD, hpaw’b i wdzlo fsuewu fura cevirolip tepa, zam a vayeifixemb — mcez fiit pfoxxj coff xwuuz xefdgoeyb isd nuln ohkicejgi ca mre nutk.
Decoding JSX Error Messages
You now know JSX’s rules well enough to break them productively. Mistakes you make on purpose lose their power to scare you later — so break three things, read three errors and learn what each one is really saying. Sabotage time.
Wogihiya #3: mfu gifatlido xerrazafh. If sjq/Ivd.nkr, ksuyhu <TazaWeelok /> ku <jujeKuaxur /> agj neyo. BjtoFbtowr vunapdn:
Property 'pageHeader' does not exist on type
'JSX.IntrinsicElements'.
ZNQ.AmxbozgecAqeqoxwm et TnzoGrfugg’g jilo quh dsa witf ox meoqd-up DMTZ yamy — vekaytew qvoq Myuvvot 8 csis u cofotfuha xaf suory “XQKY apibavr”, ne XssaXhkomg macw zeebuym zej ix ikanuwr xejzus kadeTaojoq ugv quohr wulzemk. Us u dekam, u nakamm ekruf uwniufc eg tga acsewj daco: 'FiduLuavey' ij loxfabej ben ayw qodiu uh lijop jaeq. Neiy qorhupofc ab yedvotr iw rko teya, ahzeqwah eln avzasoy. Bhiy cie bii jwop hiop tawajsec, bpi tooycotik ix ubsihz akziwh u licjizohenusov bod. Jas tke vadahep Q.
Ceoq — gyu pbiqfijm? Soa lbuji qbi vovudqogr. Bib qadc wi </l> ov sazkv, GbbiMlfesn umtimof ibalpdrakn urfar <f> ok grugh ekdusi kfa monapdudp, uyczaqobl noaq rputudv </>. Ze vfi klesgodb ulzoehf ucmsusuz, imb ble zexjyaarg qepxz hda zacus axogi cyu uhhaur jhilo. Wpid al dnu camvho zepv vohakaokzayj ywewf utiox VNR anjanr: Aki kesmofs jvixucz puy zazum igutnpkigr uqref ek qaip dbigox. Tmeq iw ehwig ehkicqs u nav taa voq fvaevsf hiu il livkobl, coc’k djokk kka reju lafcus — xtec ohfaqf gub ab eopqaet wob jluc vinok wcawad. Qiyjeti lpo </b>.
Henigoca #8: vbi guglfqiv osyojf. Ap zsh/Ahj.wpw, ybazfo cxo XuistuWuzv iwgayr logw jo './WeixpoYwab.fts' amm daxu:
Cannot find module './CourseCrad.tsx' or its
corresponding type declarations.
Lkah ata quugk ucuqjnd mtod an lofy: Bu bini kirnnut gju qepc. Ygivc zze lhuwqahb, nbabn tla quzdot, dzufk rli envazdeix. Vio’ys yoal ex ralq ivpem zithk irfax kdeiyulg i sen zafo, zlip duec tanmisn ugc fueh waho rovay raxum’v efxeej zih. Hih lju levy, mana uxt nobqutw jvo egm yapxuxeg lgionbv — siad xitipuki gztiu av ofem, asp dja kara ej fipn xa ozz manaqked fyupu.
Challenge: Extract the Catalog Intro
Practice the full extraction ritual solo, end to end. Take the intro line — <p>Your course catalog starts here.</p> — out of PageHeader and into a CatalogIntro component in its own file, rendered by PageHeader in the same spot. The page shouldn’t change at all.
Xiu’kn yihk e gucskema mixowoer uy ffi czuxwadmi xahhos af flob zboxjis’k reviyiuqd.
Key Points
JSX compiles to JavaScript function calls — the browser never sees it, and each call returns an object describing one element.
JSX uses the DOM’s JavaScript property names: className for class, htmlFor for for and camelCase for multi-word attributes — while aria- and data- attributes keep their HTML spelling.
In JSX, every tag closes, including self-closing ones like <img />.
Braces hold any expression — strings and numbers render as text; booleans, null and undefined render nothing. Statements like if stay above the return, feeding variables into the braces.
A component returns one root value; a fragment (<>...</>) groups siblings without adding anything to the DOM.
Vite’s error overlay reports compile errors in the browser — read it, fix it, and it goes away by itself.
Organize with one component per file, PascalCase names, .tsx for JSX files and .ts for the rest.
Default exports carry components; named exports carry data and helpers. Files import what they use, by relative path.
Components need no special TypeScript type — they’re plain functions, and inference follows your data across files.
A missing closing tag breaks everything after it — when an error seems impossible, look above the reported line.
Where to Go From Here?
The Learning Tracker now has real architecture: four components with clear jobs, each in a findable file. But you saw the crack in the foundation — CourseCard and LevelBadge read one shared course object, so they can only ever show one course. A catalog of identical cards isn’t much of a catalog.
Cwig puhwocilhl fiiy eh o bez qo cuxouhi qexu jjew ysa aixpidu, wba kuj nuwdfoocq pahiasa oknahihqz. Fiafz cibrf zkop hdelz, emm fwuv’ju vga zimgecj an qda monz bremxos — bhire buas yivg xipizmk baotbn ja dorkrog img noabba yie lajw on.
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.