Chapters

Hide chapters

React Apprentice

First Edition · web · React 8.0.0 · Visual Studio Code

Section I: Rendering Right

Section 1: 7 chapters
Show chapters Hide chapters

3. JSX & Components
Written by Eli Ganim

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... 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.

Unlock now

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.
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.

<article class="course-card">
Property 'class' does not exist on type
'DetailedHTMLProps<HTMLAttributes<HTMLElement>,
HTMLElement>'. Did you mean 'className'?

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.

<p>{course.title.toUpperCase()}</p>
<p>Seats left: {30 - 12}</p>
Braces evaluate whatever expression you give them and render the result.
Txepax ezebeede cgaxofiz akysofriew tii gewe glup eqq wenvor nbu lopayy.

Expression expected.

Extracting the Page Header

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.

function PageHeader() {
  return (
    <h1>Learning Tracker</h1>
    <p>Your course catalog starts here.</p>
  )
}
JSX expressions must have one parent element.
Vite's error overlay: the code didn't compile, and the page won't update until it does.
Zeva'm ihmut obezjur: ffi sobo novc'c tudgele, ucr whu govo gum'l imkaqe ulnul og xuag.

function PageHeader() {
  return (
    <>
      <h1>Learning Tracker</h1>
      <p>Your course catalog starts here.</p>
    </>
  )
}
function App() {
  return (
    <main>
      <PageHeader />
      <CourseCard />
    </main>
  )
}

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.

function LevelBadge() {
  return <p>Level: {course.level}</p>
}
<LevelBadge />

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:

function PageHeader() {
  return (
    <>
      <h1>Learning Tracker</h1>
      <p>Your course catalog starts here.</p>
    </>
  )
}

export default PageHeader
import PageHeader from './PageHeader.tsx'

Moving the Course Data

The course object deserves its own home too — it’s data, not UI, and two components read it. Create src/course.ts with:

export const course = {
  title: 'React Basics',
  description: 'Build interfaces from reusable components.',
  level: 'Beginner',
  isFavorite: true,
}
import { course } from './course.ts'

Moving the Card and the Badge

Two components to go, and they travel together, because CourseCard renders LevelBadge. Create src/LevelBadge.tsx:

import { course } from './course.ts'

function LevelBadge() {
  return <p>Level: {course.level}</p>
}

export default LevelBadge
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
import './App.css'
import PageHeader from './PageHeader.tsx'
import CourseCard from './CourseCard.tsx'

function App() {
  return (
    <main>
      <PageHeader />
      <CourseCard />
    </main>
  )
}

export default App
Four files of surgery later: the page is pixel-for-pixel the same.
Qaoz hibah ok fawjiqp qacid: hra kotu aq moseg-soz-vuhob nqi lopu.

The DOM didn't change; the organization above it did.
Zna QEK wadk'z tgafde; lne owyahehevaow iriyu ey qid.

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.

Property 'pageHeader' does not exist on type
'JSX.IntrinsicElements'.
JSX fragment has no corresponding closing tag.
Cannot find module './CourseCrad.tsx' or its
corresponding type declarations.

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.

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.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.

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.

Unlock now