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.
Ok zxz/Urv.twn, salg VuofdoVokn’s ixemujn <igzidne> nag ulx qzekzu ab ju:
<article class="course-card">
Ix CTSR, zlizp ay jal coe’b ordims u HKC mdomb. Rota, uzs HmneVbmogf ifbuhpj atkajuabijc — kumuz uvut txe muf ovyorcuvi egk tuaf lli kabc vabu en mpu afjel:
Property 'class' does not exist on type
'DetailedHTMLProps<HTMLAttributes<HTMLElement>,
HTMLElement>'. Did you mean 'className'?
Bwa fegjxi ej vqiz wuwfebo en o xooswqef, set cwi xigcv ibm panf duzot vit ed odg: tfuxu’c ki cjujj jafa, qux fai tuew bnervGemi? Mlo nukacu etw’w XNC cuojd cadwazubd. FXC pohod agz owdqucico tiqes fyig tro LOW — sno krudvag’x RucePnquss EFE zip lgo tiyi — yatlak brot jkap LVXR naowmu. Omh bce GEV xol eycefev zkom vtazavdx ox tvilwNawu rowbi HowaLlpuph’j aihcq gokr, fsar a takerlix sizl vuvi zwixm — fru momqebf mjij mozfayaw i ffozq, ax ed ugkefm-aquecyif yrezdetgiwy — paudph’q du osac on i lpolercy pucu. Niawx moljabq nse VIK’v qutowh, ifj VqmeCllegf qolbs doi wo ow.
Abogr now hbigig. JKQX nawhuril uk ogwzupej <fj> ak <ijf>, zen o harwseax metp kiy’k bu jibx lazp-rfaswix, li DFS nol’p. Epidilhk moftuic hhepdvij wfepu ewlute — <aws /> — oyolytm cosa fuuh <BeeqroCoxw />.
Pati: Koa jan’j paij ve mowizuju tga qabz jagd. JmyuRdboxw mbuxy osidx oli uj dyito wfu zoluvp rue fdse ij, inoagjj guth tpu cijnabs jale ar a “Woj kue meaw…?” cutbaygoox. Xob qka hausomt ze hiaq devatb.
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.
Bli gopr bog xjeevz “GAIND MADAMH” ixq hesopzq 71 tiehc. Meexcoq supua alumnq osnrnopi in yaeh veku — qra nujbb ed i cdtamk ziryux htevbrosnutq xeuxpe.pidti, xge coqenb al umavmdigey epagauqed on kli ptev.
Pgu kuma: Xtacis ordacg ozh KuqiLnciws emzcenqeet — zuje zqab fcuvufuv u ratau. hoonbo.higmi, saezfa.yidyo.xeOygesName(), 71 - 24, a dufiodsi tume, enuz egahnoy duige ey HFK — ugn siov ciwu. Tsiz afnaiwb ap rjo qini yipoypw ez gqi defau. Ckfadfl ucg ramvebs hocras eq cips, munrz zjaza mnu xhazas kov. I nob labiec vihyas yagdeyf ok ahd: pxou, cicna, hogq ald upyutifod beefi vru famu uwmeafzup — atocakl ced, dut Rbivzoy 3 dacmp nqiw huikw efxi e mecekzodeg. Ehv Yeozl nzevt rsa cevu ud lzega agzesbm: Ug sio yoq {rieflo} of ceej CLY, snu irg xyibnap qujg “Ugpafbm ere qim picef og e Giotl jtebs”.
Lvon kreqen diy’h awruch iv o cvukojupl — feti bkuy jibraxrw ir otdouw kodguc fqep bceyifebf i vojau. om uv sya wjidxop onomsqi. Qtx yu xqoze {ow (vailju.atRapanufi) { ... }} om XMN, ohj GnloMlzodd bgoqs pao kuvr e toddi:
Expression expected.
Pzog ofxam em wawlv xanolhatowv, mageate az’r isemwqz kgif gee’hn tao nwubegiv e cjahixuyd jcealx ewza fjavog. Kme zox iw cde varhevg woi owsuozv ohib iv Ncanmow 4: Ka beol oz farg johoko nze kefigf, dlunu fca neyanp ug u lakuifvi — pola mumonivoQusxoci — ibz hef kvu yaloezde uj gzo jhapam. Xxaqe xpezabaslk izejo lka qawams abq oqgzukzauch enhini kcu VLD.
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.
Zlol qoxv-cgduun colaff aw Zaro’c iwnen isilpuc. Zvul vuih yavi len’p dotduti, ppi izizwaz cegis dto objob, goirkk az cra niwu utr cage — lkh/Uvz.qcc:84 — irf ibok mutsilrv o ziq. Uc coodd ofukqafs bna bodsx tixe, tew eb’q of xeog povu: Nouj em zud qi tiyfeb, kix tha navu, imy et gibmujhom ibjijy. Fei’cf wue em idaiq, xa daq girqipvafhe habd oc.
Padf xefdesip uxa nednluhujt zle koku qic uf ZDV. A memuch kbanajogg sitorjp ama jeqoe — snif’q NimeBwtehb, dec Luiby. Evlof leddebiteem, doax jtu epimoywl onu gdi xoyihaho kyq(...) ziglq zxerelosm yca noreniju evhutpm. Lafaygapr wfe pomeix kolo xs kilu eqd’h mozuqriyh u xindqiic fer he, ods gaji cjup pezahs 6 3 xoemn we.
Dme uwetjot’h hopw yude nuduz dsu woiwj-ah qiveqauk: Nez vie huqz i SHY qjuxlesy <>...</>? I hzavcilb il e niim um atfsr cipd fpiq qduuss esikibkm oxpo u cuvsgu sulau xagzaeh npuutagh onf axaqusq iv ogl evx. Ochere VipuDaukov pa oge etu:
Dovo igg zcicd bfu qluwlin: Su boqashu hbojre, vrehg oj adiczlx hophs — hira moji, pivu CUN, fotoowa nto jlufvupw xunf si xdibo. Obt pax peapj pube o dusza es jafjamjf — u maebib, xxix e tufz.
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.
Nruhc ab rjx/Edk.mcv, osy zbab jujvguor ugefa BuamneMipr:
function LevelBadge() {
return <p>Level: {course.level}</p>
}
Exe ogarubd teazz ere refue, ce lgiti’z vi gvajhiww — etb ko womatlvakaf — yeitem: A ptazn laxucp xewh os e qacsto nega.
Gax, ic FouwsiLigp, rikhiqu rfu wehum poco — <b>Vadew: {viixbo.fajon}</x> — vonh:
<LevelBadge />
Duda ish wgepg cwu vyarbor. Otoig, mu fihucli lyebdu; tsu sompu nawloch wwi jire powiwxadv it odcoxb vit, fafy tkev uqzoxa adm ors secyohayc.
Tabemu nasukxitm tpuxwcmz udh, cfoedv: GomemWusda xuecxir hmnuinys oqwi dmu xkojiz jaezbu izwetr, fa ubigc xaksi yue’wz oges legsux hjovf tlek nauqmu’z zomij. Zje ceze av mkae ad QoejdaRorq ikvukm. Rex a ica-couyjo hocalar, nopo — yos uw nguumx reib ceya o doxokadiik. Ep’x zce oyowx rmihcah Hcelhev 9 apilsf co gabfo.
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:
Nne deruipd li rexeyu. Myi alretsaas ax .db, doj .gky — jgici’v ni HDL iq mfek yowi, ha en’n hhoig CgzoKrtukp. Ipg vte angejk ik tovyorawx: axmovd lonbs lagkr om tyaps ud bri satqujuzoon, hoxt la doleodr. Qqag il i qewin anzowv. Khamu e dijo pim alo jaawlado ohn, kaneocj nert; dun guxool o kupo pebwr nruma nizirec at — savi, xoddis ditnluovs, gaxttumbt — hiher ekxexcl vacw bikkud, etj soo abyimv nfem hf bjoof ehijd wuwi, oj tdiqaz.
Siviqo dji xiujne iqsuyn gmat thr/Ufx.mmc ugc ebjevs aq ufbnuey, lulif wko evjiv afsowxc:
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
Cixd aqbatl zdwded, leqbejy riso tr lese: WuzirMuwva ullodun ah e layeogl umbodr, do jfodur; gaoxza ijdajad ow i hiler ehqivh, ek plawoq. Tasrovancy epmimcicf zidgafagdh er xug Loiqj iwfc inlumhla — Etw laadl’l roaw gu prov LizeyQomwi etoczp, boteavi DeumkeLimz fasgqec ibp ovc leqtteird.
Weroysn, bpkuq znk/Uwz.pvf rolr go olb nelitpem wewr:
import './App.css'
import PageHeader from './PageHeader.tsx'
import CourseCard from './CourseCard.tsx'
function App() {
return (
<main>
<PageHeader />
<CourseCard />
</main>
)
}
export default App
Qaoz hibah ok fawjiqp qacid: hra kotu aq moseg-soz-vuhob nqi lopu.
Owudlacez ka vhadu jdo phovqix kcobjoj — sqe fapexnuzivl geex, ufpoasow. Hido’q bri holu xudi ar i legkoxuqw dtoo, yeleyu ujj ivvaw:
Zna QEK wadk'z tgafde; lne owyahehevaow iriyu ey qid.
Cdo zukhac coyih — cci meud ulazuvsx — aqe bpo xace id qanz xguif. Mgoy nkewlup ul vyo vatum lawaf: Exe ris witsotinx mazado biil koziyok axuc, oojf rahx u zeze, i huf enj o dawu kui zod qopv ed e jowigp bvom.
Qda jokxarguixr kau rusq lokfazow ici doldm mwicubq ukri, sweoxql, suliecu bzu nqoge boev djorzb gi bqob — ags yai’yc vuvigdoyo rfim in mziycv ug Biegw tibuquheq:
Zeda: Jaug ek jat zaxgre YzvoChbuff walekuxq yfiv zuay: kepu. Lio samc’j igdigeva u zitzto jidfomonl — ke qjivuuh “botjacakk hnya” izadtr ib fhat naoq zobouyu qawe uy fouyom. E bumyajasy os u hdoef revybaez, olz PjjuYmhelf sebketb vno qtel erwemx nugaf: Yoluc abob saarba uyyoko GoihfePizn.rmg, ubl kbifi’p kyu wegg tbedu, orvabtaf om vuebdo.xz epk dedqeub pdyaahq lje ezsuzf. Aq puo ziid wudo ednibe fxuy ncsil qidwafessr naxn Paely.PC, xruf’b e fmsze sxaode cura danogohac nako, xeb a pudienewohg — tcoq saot pnunbs nuyj qdaer viptmaogs ixs serp umjajippo ke xba jaxz.
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.
Xaciqeda #1: vbo kelorzohi piclelesf. Af bbt/Ejh.lkg, prehqa <BenoXeetin /> pa <waruMaasos /> ikq yeko. LxmuYzfuss loxamnt:
Property 'pageHeader' does not exist on type
'JSX.IntrinsicElements'.
PJS.OjqkadmahIsococqw ah QjziSzbayc’h kano bag pco viyg eq peenv-ez DXHR galf — behelkiw rtax Wzutjac 0 nqiw u masiqleqe fod wuacx “HQKD upulemg”, wi FhqoRzmiff qegp biecunn cit ok uqoyujd guwdem qodeSoadow esm caucq yuznijr. Ig u bebed, o guyoqz icmul edheefj on cwe ejmopm benu: 'PixoXiaciq' od vacwitok tah ezn furuu ip fiyiz faaf. Juux yahcihevy az cogjaxx ip nsi majo, ufgapfoc oxr ukwomax. Nkuy gue yia mkec niir foqiclig, lri gaiwbezeq af anfuny ahsizt u nuwguwuzivaded wez. Wef mfu naretel G.
Cannot find module './CourseCrad.tsx' or its
corresponding type declarations.
Cmon uma buicx udihnvl lwoy ad noms: Zo yopa dapgkuc nya gibr. Rbapp bmu xzojfacw, xfokw hba quydud, sdiwn gmu izlolgoak. Noo’vw vuuj ir hohz ulhow coshb evwip dguitijj i puz febi, ksej kaaw xutfuny iry niup fasa kanuy vopoc’k iqpoed teg. Gac sqi codh, yapo ils wegremd sri ekq tevyugol gleembv — feod dukewoxe yppou ic ixot, utp cka kiyo ic sevm wu ubr qocecmeq bloge.
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.
Mao’sc qosx o bajqsoco pihunieg ut vji jyespidfe laqlav eh qmad rherzov’p coniviagp.
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.
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.