Build typed React components
Outcome
Section titled “Outcome”You will refactor the static Delivery Board into a typed React component tree that renders three work items from one data array. The result will use typed props, semantic list and article elements, stable React keys, an explicit empty state, and responsive card layout.
Why this matters
Section titled “Why this matters”A front-end framework earns its place when it helps a team connect data, interface structure, and updates without manually synchronizing repeated DOM elements. Components give each visible part a named responsibility. Props make the data crossing each component boundary explicit.
This lesson changes the structure of the client without adding interaction. That boundary keeps the first React problem focused: given typed data, which component renders each part of the interface?
What you will practice
- Explain how a React function component turns props into JSX.
- Distinguish JSX syntax from HTML syntax and from the DOM nodes produced in the browser.
- Choose component boundaries from visible responsibilities instead of splitting by line count.
- Declare typed props and pass data from a parent component to a child component.
- Render a data array with map and assign each sibling a stable key from the data model.
- Keep render logic pure by treating props and imported fixture data as read-only inputs.
- Verify the component tree through type checks, semantic inspection, responsive tests, and deliberate data variations.
What is new and what is reused
Section titled “What is new and what is reused”- New: React function components, component composition, TSX and JSX rules, props, parent and child components, one-way data flow, pure rendering, conditional rendering, array-to-component mapping, stable React keys, and component file boundaries.
- Reused: The Delivery Board repository, Vite, TypeScript types, modules, arrays, objects,
map, semantic HTML, Sass, terminal checks, browser developer tools, branches, and the Stage 1WorkItemmodel.
Starting point
Before you start
- The completed Project stage 1 delivery-board repository with its accepted changes on main.
- A clean working tree and access to the private remote used for the project.
- Node.js 24 LTS, npm, VS Code, and a current browser.
- The existing WorkStatus and WorkItem types, one sampleWorkItem fixture, App component, and styles.scss file.
- No React state, event handler, route, API request, or automated component test is required yet.
- Current state
- App reads one imported WorkItem object and renders the complete static interface in one component. The data is typed, but the interface cannot yet render a collection through reusable component boundaries.
- First action
- Open the delivery-board repository, confirm that the working tree is clean, synchronize main, run the existing checks, and create feature/react-components.
- First checkpoint
- The branch contains three valid work-item fixtures and the page renders them through WorkItemList and WorkItemCard without changing the WorkItem type.
- Help trigger
- Use the nearest recovery note or ask for help if the baseline checks fail before your change, TSX reports an unexplained syntax error, a prop type error remains after the component call matches its declaration, React reports a key warning, or the card layout causes horizontal page scrolling.
Required result
Section titled “Required result”You have completed the lesson when:
feature/react-componentsbegins from the verified Stage 1mainbranch;src/data/sample-work-items.tsexports three fictional values through oneWorkItem[]annotation;- every work item has a unique, stable
id, and every label is unique within its work item; WorkItemCardreceives oneWorkItemthrough typed props and renders its complete article;WorkItemListreceives a read-only array prop and maps each work item to one semantic list item;- the JSX element directly created for each work item uses
item.idas its Reactkey; Appcomposes the product header andWorkItemListwithout rendering work-item fields itself;- zero work items produce the visible text No work items are available. instead of an empty region;
- no component changes an imported array, a prop object, or a prop array while rendering;
- the page has one
main, one pageh1, one collectionh2, onearticleandh3per work item, a description list for facts, and a semantic list for labels; - the three-card result works at 320 CSS pixels and 200% browser zoom without clipped required content or horizontal page scrolling;
- the restored source passes
npm run typecheck,npm run lint, andnpm run buildwithout an application error or React warning in the browser Console; and - one focused commit records the completed lesson state on the feature branch.
Begin from a known project state
Section titled “Begin from a known project state”Do not diagnose a previous unfinished change as part of the component lesson. Start from the accepted Stage 1 result.
git switch maingit pull --ff-onlygit statusnpm cinpm run typechecknpm run lintnpm run buildgit switch -c feature/react-componentsgit status must report a clean working tree before the branch command. If a baseline command fails, stay on main, record the first failure, and repair or ask about the Stage 1 state before you create the lesson branch.
If feature/react-components already exists, do not create a second branch with a similar name. Run git branch --list, inspect the existing branch, and continue it only when it contains your intended lesson work.
Read the current interface as one component
Section titled “Read the current interface as one component”The Stage 1 App component has several visible responsibilities:
- It defines the page shell and product heading.
- It reads the current collection of work items.
- It decides what the collection region displays.
- It renders every field for one work item.
One component can perform all four jobs, but repeated items make the boundary costly. Adding a second card would repeat the complete section markup. Changing the fact layout would require the same edit in every repeated copy.
Use three components instead:
| Component | Input | Visible responsibility |
|---|---|---|
App |
Imported project data | Compose the page shell, product header, and collection component |
WorkItemList |
Array of work items | Render the collection heading, empty state, semantic list, and stable item keys |
WorkItemCard |
One work item | Render one article with its title, description, facts, labels, and label count |
This split follows the visible interface. It does not create a component for every div, heading, or paragraph.
Connect React to the Level 2 render model
Section titled “Connect React to the Level 2 render model”In Level 2, your JavaScript selected DOM elements and used methods such as createElement, textContent, append, and replaceChildren. You decided each DOM operation and when to run it.
React keeps the same core relationship between data and visible output, but it changes your immediate task:
| Level 2 DOM code | React component code |
|---|---|
| Select the existing region | Render a component inside the React root |
| Create and update individual DOM nodes | Return JSX that describes the required output |
| Call a render function after a state change | Let React evaluate the relevant component tree after a state update |
| Keep records in arrays and objects | Keep records in arrays and objects |
| Use stable record IDs to find the correct item | Use stable record IDs as keys and later for state updates and routes |
React does not remove JavaScript, HTML semantics, or the DOM. It gives the application a component model and a renderer that reconciles the new component output with the current DOM.
A short PHP comparison
Section titled “A short PHP comparison”A PHP template can also receive data, loop over records, and reuse a partial or function for repeated markup. In a typical server-rendered PHP request, PHP runs on the server and sends produced HTML to the browser. In this Vite application, React components run in the browser, and later client-side state updates can cause React to evaluate the component tree again without requesting a complete new page.
Both approaches benefit from clear input and rendering responsibilities. TypeScript prop types check the React client during development. They do not replace server-side validation or runtime checks for external data.
Read TSX as JavaScript plus JSX
Section titled “Read TSX as JavaScript plus JSX”A .tsx file can contain TypeScript and JSX. JSX is a syntax extension that lets source code describe element structure with tag-like notation.
type ProjectSummaryProps = { name: string; itemCount: number;};
export function ProjectSummary({ name, itemCount }: ProjectSummaryProps) { return ( <section> <h2>{name}</h2> <p>{itemCount} work items</p> </section> );}Read the example in this order:
ProjectSummaryPropsdefines the component input.ProjectSummaryis a function with a capitalized name.- The function destructures
nameanditemCountfrom one props object. - The function returns JSX.
- Curly braces in JSX evaluate JavaScript expressions.
- Vite transforms the TSX for the browser. The browser does not parse this source file as HTML.
JSX rules used in this lesson
Section titled “JSX rules used in this lesson”| Requirement | JSX example | Reason |
|---|---|---|
| Capitalize custom component names | <WorkItemCard /> |
A lowercase tag name represents a built-in HTML element |
| Close every element | <WorkItemCard /> and <li></li> |
JSX requires explicit closing syntax |
| Return one root value from a component | <article>...</article> |
One JSX expression must contain the returned structure |
Use className for an HTML class |
<article className="work-item"> |
class is not the JSX property name |
| Put JavaScript expressions in braces | <h3>{item.title}</h3> |
Braces move from JSX markup into JavaScript evaluation |
| Keep HTML semantics | <ul>, <li>, <article>, <dl> |
JSX does not make a generic div more meaningful |
An expression produces a value. item.title, items.length, and items.map(...) are expressions, so they can appear inside JSX braces. An if statement does not produce a value, so use it before return or use a conditional expression inside JSX.
Expand the fixture into a typed collection
Section titled “Expand the fixture into a typed collection”Rename the singular fixture file:
git mv src/data/sample-work-item.ts src/data/sample-work-items.tsReplace its content with three fictional records:
import type { WorkItem } from "../types/work-item";
export const sampleWorkItems: WorkItem[] = [ { id: "WI-001", title: "Prepare the accessible navigation review", description: "Confirm the keyboard path, current-page state, and narrow layout before integration.", status: "planned", labels: ["frontend", "accessibility"], }, { id: "WI-002", title: "Document the component responsibilities", description: "Record which component owns the collection, one work item, and the empty result.", status: "active", labels: ["frontend", "documentation"], }, { id: "WI-003", title: "Verify the production build", description: "Run the required checks and inspect the built client at narrow and normal widths.", status: "done", labels: ["tooling", "quality"], },];The WorkItem[] annotation checks every array member against the existing model. Do not add a second type that describes the same records.
The data has two identity rules:
- every work item
idis unique in the collection; and - every label string is unique within one work item’s
labelsarray.
The lesson uses these stable values as React keys. The future API boundary must validate the same rules at runtime because a TypeScript annotation cannot validate received JSON.
Deliberately test the collection type
Section titled “Deliberately test the collection type”Change the second fixture’s status to "review", then run:
npm run typecheckThe command must reject "review" because WorkStatus permits only "planned", "active", and "done". Restore "active" and confirm that type checking passes.
If the invalid value passes, inspect the array annotation. It must be WorkItem[], not any[], and the object must not use a type assertion.
Create the component folder
Section titled “Create the component folder”Create one folder for reusable application components:
New-Item -ItemType Directory src/componentsIf the folder already exists, PowerShell can report that the item exists. Keep the existing folder and inspect its contents before adding files.
The required source structure will become:
Directorysrc/
Directorycomponents/
- WorkItemCard.tsx — Render one work-item article
- WorkItemList.tsx — Render the collection or its empty state
Directorydata/
- sample-work-items.ts — Provide the trusted internal fixture array
Directorytypes/
- work-item.ts — Define WorkStatus and WorkItem
- App.tsx — Compose the product page
- main.tsx — Render App into the fixed browser root
- styles.scss — Style the complete page
Keep the type, fixture, and component roles separate. A component can import the model it needs, but the reusable type file must not import a React component.
Render one item through typed props
Section titled “Render one item through typed props”Create src/components/WorkItemCard.tsx:
import type { WorkItem, WorkStatus } from "../types/work-item";
type WorkItemCardProps = { item: WorkItem;};
const statusLabels: Record<WorkStatus, string> = { planned: "Planned", active: "Active", done: "Done",};
function formatLabelCount(count: number): string { return `${count} ${count === 1 ? "label" : "labels"}`;}
export function WorkItemCard({ item }: WorkItemCardProps) { return ( <article className="work-item"> <h3>{item.title}</h3> <p>{item.description}</p>
<dl className="work-item-facts"> <div> <dt>Identifier</dt> <dd> <code>{item.id}</code> </dd> </div> <div> <dt>Status</dt> <dd>{statusLabels[item.status]}</dd> </div> </dl>
<h4>Labels</h4> <ul className="label-list"> {item.labels.map((label) => ( <li key={label}>{label}</li> ))} </ul> <p>{formatLabelCount(item.labels.length)} total.</p> </article> );}Read the prop boundary
Section titled “Read the prop boundary”WorkItemCardProps is the public TypeScript boundary for this component. A caller must supply an item prop whose value matches WorkItem.
<WorkItemCard item={currentItem} />
export function WorkItemCard({ item }: WorkItemCardProps) { // item is a WorkItem in this function.}React passes one props object to the component function. The parameter syntax destructures its item property. This is normal JavaScript destructuring with a TypeScript type annotation on the complete parameter.
Record<WorkStatus, string> requires one string label for every supported status. If you later add a status to WorkStatus, TypeScript will identify the missing display label here.
formatLabelCount and statusLabels stay outside the component because they do not depend on a component instance. The function receives a value, returns a string, and changes no external value.
Treat props as read-only inputs
Section titled “Treat props as read-only inputs”Do not change item, item.labels, or the imported fixture during rendering.
export function WorkItemCard({ item }: WorkItemCardProps) { item.labels.push("rendered"); // Incorrect: changes data received from the parent. return <article>{item.title}</article>;}React expects a component to produce the same output for the same props. Vite’s generated StrictMode wrapper can run render logic more than once during development to expose impure behavior. A component must still produce correct output when React evaluates it again.
Preserve semantic structure inside the component
Section titled “Preserve semantic structure inside the component”The component boundary does not appear in the browser DOM. The elements returned by the component do:
articleidentifies one self-contained work item;h3follows the collection’sh2;dl,dt, andddconnect fact names with values;ulandliidentify the labels as a list; and- visible status text communicates status without relying on color.
Do not add role="article" to the article element or role="list" to the ul. The semantic elements already provide those roles.
Render the collection and its empty state
Section titled “Render the collection and its empty state”Create src/components/WorkItemList.tsx:
import type { WorkItem } from "../types/work-item";import { WorkItemCard } from "./WorkItemCard";
type WorkItemListProps = { items: readonly WorkItem[];};
export function WorkItemList({ items }: WorkItemListProps) { if (items.length === 0) { return ( <section className="work-items"> <h2>Current work</h2> <p>No work items are available.</p> </section> ); }
return ( <section className="work-items"> <h2>Current work</h2> <ul className="work-item-list"> {items.map((item) => ( <li key={item.id}> <WorkItemCard item={item} /> </li> ))} </ul> </section> );}The prop type uses readonly WorkItem[]. A normal WorkItem[] can cross this boundary, but WorkItemList cannot call a mutating array method such as push on the received collection. This is not deep immutability: the team must still treat each received work-item object and its nested labels as read-only during rendering.
The early if handles one complete interface state. The main return can then describe the non-empty state without a nested conditional around every item.
Map data to components
Section titled “Map data to components”map calls its callback once for each work item and returns a new array of JSX elements. It does not change items.
{items.map((item) => ( <li key={item.id}> <WorkItemCard item={item} /> </li>))}The parentheses after => provide an implicit return. If you replace them with a block body, you must add return:
{items.map((item) => { return ( <li key={item.id}> <WorkItemCard item={item} /> </li> );})}Use one form consistently. The shorter form fits because this callback performs one transformation.
Give each sibling a stable key
Section titled “Give each sibling a stable key”React uses a key to match an array item with its previous rendered output when the collection changes. The key must be unique among its siblings and stable for that record.
item.id is the correct source because the data model already gives each work item a persistent identity. Do not use:
<li key={index}>...</li><li key={Math.random()}>...</li>An array index identifies a current position, not the work item. A random value changes on every render. Both choices become unsafe when later lessons insert, remove, filter, or reorder work items.
The key belongs on the JSX element created directly inside map. React consumes it and does not pass it to WorkItemCard. The card receives item.id as part of its normal item prop when it needs the visible identifier.
The label list uses label as its key because this project’s data rule makes label strings unique within one work item. If the product later permits duplicate display names, the label model must gain a separate stable identifier.
Compose the page in App
Section titled “Compose the page in App”Replace src/App.tsx:
import { WorkItemList } from "./components/WorkItemList";import { sampleWorkItems } from "./data/sample-work-items";
function App() { return ( <main className="app-shell"> <header className="product-header"> <p className="eyebrow">Module 3.1 project</p> <h1>Delivery Board</h1> <p>Review the current work before the interactive client is added.</p> </header>
<WorkItemList items={sampleWorkItems} /> </main> );}
export default App;App imports data and passes it down. WorkItemList receives the array and passes one item down. WorkItemCard renders that item. Data moves from parent to child through props.
No child reaches into App to find the fixture. This one-way data flow makes each input visible at the component call.
Deliberately test the prop type
Section titled “Deliberately test the prop type”In WorkItemList, temporarily replace the card call:
<WorkItemCard item={item.title} />Run npm run typecheck. TypeScript must report that a string is not assignable to WorkItem. Restore item={item} and confirm that the command passes.
If the incorrect call passes, inspect WorkItemCardProps. Its item property must use WorkItem, not any or unknown.
Checkpoint: Typed data crosses explicit component boundaries
- What now works
- App passes three typed work items to WorkItemList, the list maps them through stable IDs, and WorkItemCard renders each complete semantic article. The deliberate incorrect prop fails and the restored source passes type checking.
- Files changed
src/data/sample-work-items.ts, src/components/WorkItemList.tsx, src/components/WorkItemCard.tsx, src/App.tsx- What remains
- Adapt the Stage 1 styles, test the empty state and identity assumptions, and complete the production verification.
- Next action
- Open styles.scss, remove the single-card top-margin rule, and add the collection and responsive grid rules from the next section.
- If it does not work
- Confirm the renamed data import, component export names, relative import paths, prop declarations, one returned JSX root, closing tags, and the first type-check error in that order.
Adapt the layout for repeated cards
Section titled “Adapt the layout for repeated cards”The Stage 1 stylesheet places one .work-item after the product header. The new ul owns collection spacing, so replace this rule:
.work-item { margin-block-start: $space-6;}Add these rules after the shared .product-header, .work-item surface rule:
.work-items { margin-block-start: $space-8;}
.work-item-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); gap: $space-6; padding: 0; list-style: none;}
.work-item-list > li { min-width: 0;}
.work-item { height: 100%;}Extend the existing text-wrapping and heading-margin selectors so they include the new heading levels:
h1,h2,h3,h4,p,dd,code,li { overflow-wrap: anywhere;}
h1,h2,h3,h4 { margin-block-start: 0;}Replace the corresponding Stage 1 selector blocks instead of keeping two competing versions.
The grid can create several columns when cards have sufficient room. minmax(min(100%, 20rem), 1fr) lets a track become no wider than the available container on a narrow screen. min-width: 0 permits a grid item to shrink instead of forcing horizontal overflow.
The semantic hierarchy remains independent of the layout. A one-column phone result and a multi-column desktop result contain the same list and article structure.
Check the development result
Section titled “Check the development result”Run the development server in Terminal 1 if it is not already active:
npm run devOpen the reported URL and confirm:
- The page shows Delivery Board once.
- Current work appears once.
- Three cards show IDs
WI-001,WI-002, andWI-003. - Statuses display as Planned, Active, and Done.
- Every card shows two visible labels and 2 labels total.
- Cards form columns only when the available width supports readable cards.
- The Console contains no React key warning or application error.
If it does not work
Section titled “If it does not work”- If the page is blank, read the first terminal transformation error and the first Console error. Fix the earliest source error before changing CSS.
- If the import cannot be resolved, confirm the exact plural file name, export name, capitalization, and relative path.
- If React reports that an element type is invalid, compare named exports and named imports.
WorkItemListandWorkItemCarduse braces in both places. - If React reports a key warning, confirm that
key={item.id}is on theliinside the work-itemmap, and confirm that all three fixture IDs differ. - If a card exceeds the viewport, inspect the grid track,
min-width, fixed widths, and long text before addingoverflow-x: hidden. Hiding overflow would conceal the defect.
Test the component assumptions deliberately
Section titled “Test the component assumptions deliberately”A passing page with one data set is weak evidence. Change one assumption at a time, observe the expected result, and restore the submitted fixture after each test.
Test the empty state
Section titled “Test the empty state”In App, temporarily pass an empty array:
<WorkItemList items={[]} />Expected result:
- the product header remains visible;
- Current work remains visible;
- No work items are available. appears;
- no empty
ulappears; and - the Console has no application error.
Restore items={sampleWorkItems}.
Test stable identity
Section titled “Test stable identity”Temporarily change the third fixture ID from WI-003 to WI-002. The development Console must report a duplicate-key warning for the work-item siblings. Restore WI-003 and reload. The warning must no longer appear.
A TypeScript string type cannot prove uniqueness between records. This test checks a runtime data invariant that the future API validator must enforce.
Test long content
Section titled “Test long content”Temporarily replace one title and one label with long unbroken test strings. Check the narrow viewport and confirm that the text wraps without increasing the page width. Restore the readable fixture text.
Use DevTools to inspect the final structure. The React component names appear in React tooling when installed, but the Elements panel shows the produced semantic HTML. Confirm the final page contains:
main header h1 section h2 ul li article h3 dl h4 ulThe repeated li > article branch must occur three times. This outline describes required relationships; it does not require these elements to be direct children when the supplied code includes additional meaningful wrappers.
Checkpoint: The component interface handles collection variations
- What now works
- The three-card layout wraps at narrow widths, the empty array produces explicit text, duplicate identity produces a deliberate warning, restored unique IDs remove that warning, and long content does not widen the page.
- Files changed
src/styles.scss, src/App.tsx, src/data/sample-work-items.ts, Browser Elements panel, Browser Console- What remains
- Run the complete source and production checks, inspect the final diff, and record the lesson commit.
- Next action
- Restore all submitted fixtures, stop any temporary data experiment, and run typecheck, lint, and build from the project root.
- If it does not work
- Restore the exact lesson data and App call, compare the collection CSS with the required selectors, then diagnose the first remaining source, layout, or Console failure.
Verify the completed component tree
Section titled “Verify the completed component tree”Run the source checks from the project root:
npm run typechecknpm run lintnpm run buildStart the production preview only after all three commands pass:
npm run previewUse the exact URL reported by Vite. Verify:
- a normal desktop width;
- 320 CSS pixels wide;
- 200% browser zoom;
- keyboard scrolling through the complete document;
- the heading and semantic list structure in the Elements panel;
- all three work-item fields and label lists;
- no clipped or overlapping required text;
- no horizontal page scrollbar; and
- no React warning or red application error in the Console.
Stop the preview with Ctrl+C after the checks.
Inspect and commit the focused change
Section titled “Inspect and commit the focused change”Review the work before staging it:
git status --shortgit diffThe expected source change is:
- one renamed data file;
- two new component files;
- one changed
App.tsx; and - one changed
styles.scss.
Generated dist output and node_modules must remain outside the diff.
Stage the exact paths and inspect the staged diff:
git add src/App.tsx src/styles.scss src/components/WorkItemCard.tsx src/components/WorkItemList.tsxgit add src/data/sample-work-item.ts src/data/sample-work-items.tsgit diff --cachedgit commit -m "feat: render work items with typed components"git statusStaging both the old and new fixture paths lets Git record the rename even if its similarity detection displays the change as a delete and an add. The final git status must report a clean working tree on feature/react-components.
Do not merge this branch as an unreviewed shortcut. The next lessons continue from this component result, and Project stage 2 will define the required review and integration evidence.
Self-check
Complete these checks against the required result.
- Confirm that the lesson branch began from the accepted and verified Stage 1 main branch.
- Point to the separate model, fixture, page, collection, item, entry, and style files and state the responsibility of each.
- Confirm that sampleWorkItems has a WorkItem[] annotation, three unique IDs, and unique labels within each work item.
- Change one status to review, confirm that type checking fails, restore the supported status, and confirm that type checking passes.
- Point to WorkItemCardProps and explain how the item prop crosses from the component call into the function parameter.
- Pass item.title to WorkItemCard, confirm that type checking rejects the string, restore item, and confirm that type checking passes.
- Confirm that WorkItemList receives readonly WorkItem[] and does not sort, push into, or otherwise change its prop array.
- Point to the map callback, stable item.id key, and separate item prop and explain the responsibility of each.
- Confirm that label strings are valid keys only because each item keeps its labels unique.
- Pass an empty array and confirm that the visible empty message replaces the semantic work-item list without an error.
- Create a temporary duplicate work-item ID, observe the React key warning, restore unique IDs, and confirm that the warning is gone.
- Inspect the final DOM and confirm one main, one h1, one collection h2, and three semantic list-item and article branches with h3 headings.
- Confirm that each card uses visible status text, a description list for facts, and a semantic list for labels.
- Verify the restored result at 320 CSS pixels and 200 percent zoom with no clipped content or horizontal page scrolling.
- Run typecheck, lint, and build and inspect the production preview without an application error or React warning.
- Inspect the focused Git diff and confirm that the final lesson commit leaves the feature branch clean.
Explanation: component boundaries form an interface contract
Section titled “Explanation: component boundaries form an interface contract”The finished source separates three kinds of change:
- A change to the reusable work-item data shape belongs in
work-item.ts. - A change to how one work item looks belongs in
WorkItemCard. - A change to collection behavior, such as an empty result or future filtering, belongs in
WorkItemListor its nearest state-owning parent. - A change to page composition belongs in
App.
Typed props connect these responsibilities. If one component starts needing many unrelated props, or a child must know how its parent stores unrelated state, reassess the boundary. A useful component has a focused visible job and an input surface that describes that job.
React components remain JavaScript functions. TypeScript checks their declared inputs, Vite transforms their TSX, React evaluates their output, and React DOM applies the required browser changes. None of those tools replaces semantic HTML, runtime validation, or manual accessibility checks.
Official references
Section titled “Official references”- Your First Component — React function-component structure and composition
- Writing Markup with JSX — JSX syntax rules
- Passing Props to a Component — parent-to-child data and read-only props
- Rendering Lists —
map, stable keys, and list identity - Keeping Components Pure — predictable render logic and development checks
Optional extensions
Section titled “Optional extensions”The required lesson is complete before these routes. Use a separate commit for any selected extension.
Next step or safe stopping point
Section titled “Next step or safe stopping point”The required result is safely paused when the three typed cards and empty state work, all final checks pass, the deliberate failures are restored, the component commit exists, and git status is clean on feature/react-components.
The next lesson adds events and React state to this same component result. It will decide which component owns changing data and will keep derived values out of state.