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

4. Props, Children, and Composition
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 got under JSX’s hood and gave the Learning Tracker real structure: PageHeader, CourseCard and LevelBadge, each a focused component in its own file. You also left the chapter staring at a crack in the foundation — every component reads the same shared course object, so your “reusable” card can only ever show one course.

This chapter fixes that with props, React’s mechanism for passing data into a component. Props do for components what arguments do for functions, and they change everything: The same CourseCard will render any course you hand it, and the catalog finally becomes a catalog.

Along the way, you’ll teach TypeScript to enforce each component’s contract, meet children — the built-in prop that lets components wrap other UI — and build two workhorses you’ll use for the rest of the book — a Card container and a Button. You’ll finish with a feel for the hardest question in React — where one component should end and the next begin.

Feeling the Pain of Hardcoding

Before fixing the problem, make it undeniable. In src/App.tsx, render the card three times:

<main>
  <PageHeader />
  <CourseCard />
  <CourseCard />
  <CourseCard />
</main>

Save and check the browser:

Three cards, one course: reuse without inputs is just repetition.
Three cards, one course: reuse without inputs is just repetition.

Three identical cards. Of course they’re identical — CourseCard reads the one shared course object, so it can only ever print the same thing, like a rubber stamp.

How would you show a second course today? You’d have to copy the entire component. Don’t type this — just imagine your src folder filling up with it:

function CssLayoutCard() {
  return (
    <article>
      <h2>CSS Layout</h2>
      ...
    </article>
  )
}

One near-identical component per course, with the markup duplicated in every one. Fix a typo in the card layout, and you fix it twelve times. This is the moment where every React developer needs what’s coming next.

Props

A component is a function — you’ve known that since Chapter 2. And functions have a famous solution for “same logic, different values” — arguments. React’s version of arguments is props (short for properties).

{
  course: reactBasics
}
Props travel in one direction: from parent to child, down the component tree.
Gkizl ckegup ul iwi xugabgeef: jgab sejohj ta gvotv, rins wgi yoljuxejr ywio.

Passing Your First Prop

Start small with one prop — the course title. In src/CourseCard.tsx, add this type above the component and change the function’s first line, so the file starts like this:

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

type CourseCardProps = {
  title: string
}

function CourseCard(props: CourseCardProps) {
<h2>{props.title}</h2>
Property 'title' is missing in type '{}' but required in
type 'CourseCardProps'.
<CourseCard title="React Basics" />
<CourseCard title="CSS Layout" />
<CourseCard title="Modern TypeScript" />
One component, three titles — your first prop at work.
Ejo kukpuhedf, tzluu kecred — giis novxz cdal eq xosd.

function CourseCard({ title }: CourseCardProps) {

Passing Numbers and Booleans

The title rode into the component in quotes, because it’s a string. Everything else rides in braces — and the card has two values waiting for exactly that: a course’s length and whether it’s a favorite. Extend the contract in src/CourseCard.tsx:

type CourseCardProps = {
  title: string
  durationHours: number
  isFavorite: boolean
}
function CourseCard({
  title,
  durationHours,
  isFavorite,
}: CourseCardProps) {
<p>{durationHours} hours</p>
<CourseCard
  title="React Basics"
  durationHours={6}
  isFavorite={true}
/>
<CourseCard
  title="CSS Layout"
  durationHours={4}
  isFavorite={false}
/>
<CourseCard
  title="Modern TypeScript"
  durationHours={9}
  isFavorite={false}
/>
Strings in quotes, numbers and booleans in braces — each card now owns its title, duration and favorite state.
Xxxunvj oz doetil, mabciyw ebr jeajiimv ot bziriy — oowk qagv hox eddw oqm qaype, bazonaev epv budahise bwivo.

Passing the Whole Course

You could keep going one prop at a time — description and level are still waiting — until every call looks like this. Don’t type it:

<CourseCard
  title="React Basics"
  description="Build interfaces from reusable components."
  level="Beginner"
  durationHours={6}
  isFavorite={true}
/>
export const reactBasics = {
  title: 'React Basics',
  description: 'Build interfaces from reusable components.',
  level: 'Beginner',
  durationHours: 6,
  isFavorite: true,
}

export const cssLayout = {
  title: 'CSS Layout',
  description: 'Arrange pages with flexbox and grid.',
  level: 'Intermediate',
  durationHours: 4,
  isFavorite: false,
}

export const modernTypescript = {
  title: 'Modern TypeScript',
  description: 'Master types for safer, clearer code.',
  level: 'Advanced',
  durationHours: 9,
  isFavorite: false,
}
import LevelBadge from './LevelBadge.tsx'

type CourseCardProps = {
  course: {
    title: string
    description: string
    level: string
    durationHours: number
    isFavorite: boolean
  }
}

function CourseCard({ course }: CourseCardProps) {
  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 level={course.level} />
      <p>{course.durationHours} hours</p>
      <p>{favoriteMessage}</p>
    </article>
  )
}

export default CourseCard
type LevelBadgeProps = {
  level: string
}

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

export default LevelBadge
import './App.css'
import PageHeader from './PageHeader.tsx'
import CourseCard from './CourseCard.tsx'
import {
  reactBasics,
  cssLayout,
  modernTypescript,
} from './courses.ts'

function App() {
  return (
    <main>
      <PageHeader />
      <CourseCard course={reactBasics} />
      <CourseCard course={cssLayout} />
      <CourseCard course={modernTypescript} />
    </main>
  )
}

export default App
Three genuinely different cards from one component and three objects.
Dlkaa caxeagijq motcaqiwx tirwv tsar ibu siqzamicm apg gtraa idzarsb.

Handling Optional Props

Real data is never uniform. Say the CSS Layout course is self-paced — it has no fixed length. In src/courses.ts, delete the durationHours: 4, line from cssLayout and save. TypeScript objects in App.tsx:

Property 'durationHours' is missing in type '{ title: string;
description: string; level: string; isFavorite: boolean; }' but
required in type ...
durationHours?: number
let durationLabel = 'Self-paced'
if (course.durationHours !== undefined) {
  durationLabel = course.durationHours + ' hours'
}
Optional data, handled honestly: no duration means self-paced, not a blank.
Ulbeases mona, secpbuj lusucjrb: ju buwoveod xeexk paql-ladef, kod u rsahz.

Children: The Prop You Don’t Pass by Name

Every prop so far carried data. One special prop carries UI. Compare these two lines of JSX you’ve written in this book:

<CourseCard course={reactBasics} />
<main>...</main>
import type { ReactNode } from 'react'

type CardProps = {
  children: ReactNode
}

function Card({ children }: CardProps) {
  return <article>{children}</article>
}

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

Building a Reusable Button

The catalog needs actions — starting with a way to mark favorites. Real behavior arrives with events in Chapter 8, but the button itself, with a proper typed contract, you can build today. Create src/Button.tsx:

type ButtonProps = {
  label: string
  onClick: () => void
  variant?: 'primary' | 'ghost'
}

function Button({
  label,
  onClick,
  variant = 'primary',
}: ButtonProps) {
  return (
    <button
      type="button"
      className={'button ' + variant}
      onClick={onClick}
    >
      {label}
    </button>
  )
}

export default Button
import Button from './Button.tsx'
function noteFavoriteClick() {
  console.log('Favorites arrive in Chapter 8!')
}
<Button
  label="Add to favorites"
  onClick={noteFavoriteClick}
/>
Every card grows a button — typed, labeled and waiting for Chapter 8.
Uwelg wukr fyajk a koqkaj — fwnah, gijibuv atc heavetl zor Qselyiy 2.

Type '"primry"' is not assignable to type
'"primary" | "ghost" | undefined'. Did you mean '"primary"'?

Drawing Component Boundaries

You’ve now built components that take data (CourseCard, LevelBadge), a component that takes UI (Card) and a component that takes behavior (Button). The remaining skill is judgment: How much should one component do?

Prop overload versus composition: the same card, designed two ways.
Ymid ituvbaad semhet wikyijutaeg: hho tero noss, nukuynib cma sewj.

Challenge: Build a Section Heading

The catalog’s cards now sit directly under the page header. Give the section a proper introduction: Build a SectionHeading component and render it in App, between PageHeader and the first card.

<SectionHeading
  title="Course Catalog"
  subtitle="Three courses and counting."
/>

Key Points

  • Props are a component’s inputs: JSX attributes collected into one object and passed to your function, like arguments.
  • Props flow one way, parent to child, and are read-only — a component never modifies what it receives.
  • A type alias like CourseCardProps declares the contract; TypeScript then flags every caller that breaks it, including missing props.
  • Destructuring in the parameter list — { course }: CourseCardProps — unpacks props without the props. prefix.
  • Group values that always travel together into an object prop; pass objects, numbers and booleans in braces, literal strings in quotes.
  • A ? makes a prop optional; handle the undefined case deliberately, with a fallback or a default value in the destructuring.
  • children is the built-in prop holding whatever a caller nests between your component’s tags; type it as ReactNode.
  • Function props like onClick: () => void pass behavior — and you pass the function itself, never the parentheses.
  • A union of exact strings ('primary' | 'ghost') turns invalid values into compile-time errors.
  • Watch for prop overload — a growing, interdependent props list means the component wants to be split and composed.

Where to Go From Here?

The catalog is honest now — three real courses, one reusable card. But look at App.tsx — you’re still writing one <CourseCard /> line per course, importing each course by name. Course number thirty would mean thirty imports and thirty JSX lines, maintained by hand.

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