React Essentials: From Imperative to Declarative UI
A hands-on introduction to React for students who already know C++ and Python and have completed the Node.js tutorial. Assumes basic familiarity with HTML tags and CSS classes (a refresher is provided in Step 1). Learn to think declaratively, build components, manage state, and compose real UIs — all live in your browser.
Hello, React! — Declarative vs. Imperative
Learning objective: After this step you will be able to explain the difference between imperative and declarative UI programming, and modify a simple React component.
The Paradigm Shift
You know how to manipulate the DOM the imperative way — you tell the browser how to do it, step by step:
// Imperative: You write the HOW
const h1 = document.getElementById('greeting');
h1.textContent = 'Hello, CS 35L!';
h1.style.color = '#2774AE';
React asks you to think declaratively — you describe what the UI should look like for a given moment, and React figures out the minimal DOM updates needed to get there:
// Declarative (React): You describe the WHAT
function App() {
return <h1 className="greeting">Hello, CS 35L!</h1>;
}
| Imperative (Vanilla JS / C++) | Declarative (React) | |
|---|---|---|
| Mindset | How to reach the state | What the state should look like |
| Analogy | Turn-by-turn GPS directions | Dropping a pin on the destination |
| DOM updates | You call element.textContent = ... |
React diffs the Virtual DOM and patches only what changed |
| Bugs | Easy to forget a step, leaving stale UI | React re-renders the whole component; inconsistent state is much harder |
A Note About the Paradigm Shift
The declarative mindset feels strange at first — you are used to telling the computer exactly what to do, step by step. In React, you describe the destination and let React figure out the route. This shift takes time. If it feels unnatural, that is a sign you are learning something fundamentally new, not that you are doing it wrong. Every React developer went through this disorientation.
HTML Tags — A Quick Reminder
React’s JSX uses the same tags as HTML. Here are the ones you will see throughout this tutorial:
| Tag | Purpose | Example |
|---|---|---|
<h1> – <h6> |
Headings (h1 = largest) | <h1>Hello!</h1> |
<p> |
Paragraph of text | <p>Welcome to React.</p> |
<div> |
Generic container (no visual meaning) | <div>...</div> |
<span> |
Inline container (for styling a word or phrase) | <span>Sale!</span> |
<button> |
Clickable button | <button>Click me</button> |
<ul>, <li> |
Unordered list and list items | <ul><li>Item</li></ul> |
<img> |
Image (self-closing) | <img src="photo.jpg" /> |
These tags describe structure — what each piece of content is. They say nothing about how it looks. That is the job of CSS.
What Is CSS?
CSS (Cascading Style Sheets) controls how elements look — colors, spacing, fonts, borders, and layout. A CSS class is a reusable set of styles that you apply to elements by name:
.greeting { color: tomato; font-size: 24px; }
In React, you attach a CSS class with the className prop (not class — that is a reserved JavaScript keyword):
<h1 className="greeting">Hello!</h1>
This tutorial loads Bootstrap (a CSS library) automatically, so layout and typography are handled for you. The styles.css file is for your own custom styles. You do not need to learn CSS for this tutorial — styling is provided in every step after this one. Here, you will make one small change to get comfortable with the idea.
JSX: A Quick Preview
The <h1>...</h1> syntax inside JavaScript is called JSX. It looks like HTML, but it is not — Babel compiles it to React.createElement(...) calls that build a lightweight JavaScript object tree (the Virtual DOM). You will learn the details and rules of JSX in the next step.
Can You Beat the Renderer?
Before changing anything, look at the App component. Predict: what does {name} inside the JSX evaluate to? What does className="greeting" connect to in styles.css? Write your predictions, then read on.
Task
The preview shows a greeting component. Make two changes:
- In
App.jsx: Change"World"to another name in thenamevariable - In
styles.css: Change the color fromtomatoto#2774AE(or any other color)
The preview rebuilds automatically when you save (Ctrl+S). Use ↻ Refresh if needed.
.greeting {
color: tomato; /* Task 2: Change this color */
}
function App() {
const name = "World"; // Task 1: Change this to your name
return (
<div className="p-4">
<h1 className="greeting display-6 fw-bold">
Hello, {name}!
</h1>
<p className="mt-2 text-secondary">Welcome to React.</p>
</div>
);
}
// Mount — you don't need to change this
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Hello, React! — Knowledge Check
Min. score: 80%
1. In vanilla JS you’d write h1.textContent = newTitle to update a heading. What is the declarative React equivalent?
React’s mental model: you change the data, not the DOM. React calls your component function, builds a new Virtual DOM tree, diffs it against the previous one, and patches only what changed. You describe what the UI looks like for a given set of data; React figures out how to get there. In Step 4 you will learn about useState, which makes data changes automatically trigger this cycle.
2. What does Babel compile <h1>Hello</h1> into?
JSX is syntactic sugar. Babel transforms it to React.createElement(type, props, ...children) calls, which return plain JavaScript objects — the Virtual DOM. No real DOM nodes are created at this stage. React’s reconciler does that later, and only for the parts that actually changed.
3. A teammate proposes: “Instead of learning React, let’s just use vanilla JavaScript with document.getElementById — it’s more direct and we already know it.”
Evaluate this suggestion for a project with 50+ interactive UI components that update frequently.
This is a real trade-off. For a static page or 2-3 interactive widgets, vanilla JS is perfectly fine and simpler. But as interactivity scales, manually synchronizing data and DOM becomes the #1 source of bugs. React’s value proposition is eliminating that synchronization — you declare what the UI looks like for each state, and React handles the rest.
Components & JSX — Fixer-Upper
Learning objective: After this step you will be able to identify and fix common JSX syntax errors, and explain why JSX differs from HTML.
Components Are Just Functions
In C++ and Python you build programs by composing functions. React works the same way, but functions return JSX (UI) instead of numbers or strings.
// SUB-GOAL: Define the component as a function returning JSX
// Python function: React component:
def greet(name): function Greet({ name }) {
return f"Hello, {name}" return <p>Hello, {name}!</p>;
}
Components let you split a complex UI into small, reusable pieces — exactly like how you extract a C++ helper function to avoid repeating code.
JSX Rules — Where HTML Instincts Break
JSX looks like HTML but is actually JavaScript. These four rules catch most beginners:
| Rule | Wrong (HTML instinct) | Correct (JSX) |
|---|---|---|
| CSS class attribute | class="..." |
className="..." (class is a JS keyword) |
| Self-closing tags | <img src={u}> |
<img src={u} /> (required in JSX) |
| Inline style | style="color:red" |
style={{color: 'red'}} (JS object, not CSS string; prefer CSS classes when possible) |
| Multiple root elements | return <h1/><p/> |
return <><h1/><p/></> (single root required) |
| Component names | <card /> |
<Card /> (must be capitalized) |
| Embed JS expressions | <p>name</p> |
<p>{name}</p> (curly braces for expressions) |
Can You Beat the Renderer?
Before fixing the bugs below: look at the Badge component’s style prop. It says style="background: color;". Predict: what is wrong with this syntax? Write your prediction, then fix it.
Fixer-Upper: Three Classic JSX Bugs
The file below has three bugs that prevent it from rendering correctly.
Task
- Find and fix all three JSX bugs in
App.jsx(hint: use the table above) - Once it renders, add a third
<Badge>below the existing two, with a label of your choice and a different color
The Badge component is already defined — you just need to use it.
// A reusable Badge component
// Props: label (string), color (string — any CSS color)
function Badge({ label, color }) {
return (
<span className="badge rounded-pill fw-semibold" style="background: color;">
{label}
</span>
);
}
function App() {
return (
// BUG: Multiple root elements without a wrapper
<h1 class="h3 mb-3">My Badges</h1>
<div className="d-flex gap-2 mt-3">
<Badge label="React" color="#61dafb" />
<Badge label="JavaScript" color="#f7df1e" />
{/* Task: Add a third <Badge> here */}
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Components & JSX — Knowledge Check
Min. score: 80%
1. Why does React use className instead of class for CSS classes?
JSX is JavaScript, and class is a reserved keyword in JavaScript (used for ES6 classes). Using class inside JSX would cause a syntax error. className maps directly to the DOM property element.className, so it works identically at runtime.
2. Why must JSX components return a single root element?
JSX compiles to React.createElement(...), which returns a single JS object. A function can’t return <A/><B/> any more than it can return 1 2 — only one expression is valid. Wrap siblings in a <div> or the zero-overhead fragment <>...</> (compiles to React.Fragment).
3. How do you write an inline style with font-size: 18px and color: red in JSX?
The JSX style prop takes a JavaScript object, not a CSS string. CSS property names become camelCase (fontSize, not font-size). Values are strings (or numbers for unitless properties). The double braces {{ }} are: the outer {} for a JSX expression, the inner {} for the object literal.
4. Analyze this code. A student writes function card() { return <div>A card</div>; } and uses <card />. It renders an empty box. Why?
React uses the capitalization of a JSX tag to decide: lowercase → HTML element (passes to the browser’s DOM), uppercase → React component (calls your function). <card /> silently becomes an unknown HTML element. The fix: rename to Card and use <Card />.
5. Which of these are correct JSX? (Select all that apply) (select all that apply)
<img ... /> is correct — self-closing tags are required in JSX. <p>{expression}</p> is correct — any JS expression works inside {}. class is a JS reserved word; use className. Browser event handlers use camelCase in JSX: onClick, not onclick.
Props — Parameterizing Components
Learning objective: After this step you will be able to implement components that accept props, and explain why props are read-only.
Props Are Function Arguments
A component with no props is like a function with no parameters — useful, but limited. Props let you pass data into a component, exactly like calling a function with arguments.
// SUB-GOAL: Define a component that accepts props via destructuring
// C++: void printCard(string name, double price) { ... }
// Python: def render_card(name, price): ...
// React — defining the component:
function ProductCard({ name, price }) {
return (
<Card>
<Card.Body>
{/* SUB-GOAL: Use props to render dynamic content */}
<h3>{name}</h3>
<p>${price.toFixed(2)}</p>
{/* TODO: Add more elements here */}
</Card.Body>
</Card>
);
}
// SUB-GOAL: Use the component with specific prop values
<ProductCard name="Laptop" price={999.99} />
<ProductCard name="Mouse" price={29.99} />
Destructuring: Unpacking Props
The { name, price } syntax in the function signature is called destructuring — it unpacks properties from the props object into separate variables. If you have used C++17 structured bindings, it works the same way:
C++: const auto [name, price] = product; // structured binding
Python: name, price = product // tuple unpacking
React: function Card({ name, price }) { ... } // destructuring
Key Props Rules
- Props flow one way — from parent to child, never the other direction
- Props are read-only inside the component (like
constfunction parameters in C++) - Any JS value can be a prop: string, number, boolean, object, array, function, or another component
- Syntax: String props use quotes (
title="Hello"). All other types — numbers, booleans, expressions — use braces:price={99.99},active={true},onClick={handleClick}
Conditional Rendering with &&
Task 4 below asks you to show a badge only when onSale is true. In C++ or Python, you would use an if statement. But JSX is an expression (it produces a value), not a block of statements — you cannot write if inside it, just like you cannot write if inside cout << ... or an f-string.
Instead, React uses JavaScript’s && (logical AND) operator:
{soldOut && <Badge bg="danger">Sold Out!</Badge>}
How it works: JavaScript evaluates the left side first. If soldOut is false, it short-circuits — the right side is never evaluated, and React renders nothing (because false is ignored in JSX). If soldOut is true, JavaScript returns the right side, and React renders the Badge.
This is the React equivalent of:
# Python — you can't embed if-statements in f-strings either
sale_text = "Sale!" if on_sale else ""
You will learn more conditional rendering patterns (ternary, early return) in Step 6.
Can You Beat the Renderer?
Before writing any code, predict: what will the ProductCard look like when onSale is true vs false? Now that you know the && pattern, write the JSX in your head, then implement it.
Task
The ProductCard component skeleton is provided. Complete it so that it:
- Displays the product
nameas an<h3> - Displays the
priceformatted to two decimal places (useprice.toFixed(2)) - Displays the
descriptionin a<p>tag - Shows a “Sale!” badge only when
onSaleistrue
The App function already passes the right props — you only need to build the card.
Bonus round: After passing the tests, add a third ProductCard in App with your own product data and onSale value. Notice how the same component renders differently based on the data you pass — that is the power of props.
const { Card, Badge } = ReactBootstrap;
function ProductCard({ name, price, description, onSale }) {
// Task: Build the card UI using the four props above.
// Requirements:
// 1. <h3> showing name
// 2. Price formatted to 2 decimal places
// 3. <p> showing description
// 4. A "Sale!" badge (shown only if onSale is true)
//
// Hint: Use <Badge bg="danger">Sale!</Badge> for the badge
return (
<Card className="product-card">
<Card.Body>
{/* Your code here */}
</Card.Body>
</Card>
);
}
function App() {
return (
<div className="p-4 d-flex gap-4 flex-wrap">
<ProductCard
name="Mechanical Keyboard"
price={129.99}
description="Tactile switches, RGB backlit, compact 75% layout."
onSale={true}
/>
<ProductCard
name="USB-C Hub"
price={49.99}
description="7-in-1 hub: 4K HDMI, 3× USB-A, SD card, 100W PD."
onSale={false}
/>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Props — Knowledge Check
Min. score: 80%
1. Inside a component, you have function Card({ title }) { title = 'New'; ... }. What is wrong with this?
Props are immutable inside a component. Mutating them would corrupt the parent’s data and break the predictable top-down data flow that React relies on. If a component needs to change a value, it should use useState (local state) or call a function passed as a prop from the parent.
2. Which of these is the correct way to pass a number prop to a component?
String literals can be passed directly: label="Hello". All other values — numbers, booleans, objects, arrays, functions — must be wrapped in {}: price={99.99}, active={true}, items={[1, 2, 3]}. Without {}, React would interpret 99.99 as a malformed attribute, not a number.
3. Analyze this: <Card title="React" /> and <Card title={"React"} /> produce the same result. When would they differ?
For plain string values, both are equivalent. But {} is required for any JS expression:
title={user.name}, title={isAdmin ? 'Admin' : 'User'}, title={getTitle()}.
Only string literals can use the quote syntax. This is a common source of confusion
for beginners who try price=99.99 (without braces) and get unexpected results.
4. A ProductCard receives price as a prop and renders ${price.toFixed(2)}. What happens if the parent passes price={undefined}?
undefined.toFixed(2) is a runtime error — undefined has no methods.
In production, you would guard against this with a default value:
function ProductCard({ price = 0 }) { ... } or (price ?? 0).toFixed(2).
React does not provide automatic fallbacks for missing props.
5. Arrange the lines to build a Greeting component that accepts name and emoji props and renders them.
(arrange in order)
function Greeting({ name, emoji }) {return ({emoji} Hello, {name}!
);}
function Greeting(name, emoji) {emoji Hello, name!
The correct signature uses destructuring { name, emoji } to unpack props — the distractor omits the braces, which would receive the entire props object as name and undefined as emoji. Inside JSX, props must be wrapped in {curly braces} to be evaluated as expressions — without them, React renders the literal text ‘emoji’ and ‘name’.
useState — Making Components Remember
Learning objective: After this step you will be able to implement interactive components using
useStateand explain why regular variables don’t trigger re-renders.
Try It First (Productive Failure)
The Hardest Paradigm Shift
This step is where most students get stuck. The idea that changing a variable doesn’t update the UI — and that you need a special React function to do it — feels deeply wrong after years of imperative programming. That confusion is normal and expected. Every React developer had the same “but why doesn’t this just work?” moment.
Before reading further, look at the counter code below. It doesn’t work — clicking +1 does nothing. Spend 2 minutes trying to fix it using what you know from C++ and Python. What approaches did you try? Why didn’t they work?
Why Regular Variables Don’t Work
In C++, a class stores data in member variables that persist across method calls. In React, calling your component function is like constructing a fresh object each time — local variables are reset on every render.
// BROKEN — count is reset to 0 every time the button is clicked
function Counter() {
let count = 0; // ← destroyed on each re-render
return <button onClick={() => count++}>{count}</button>;
}
How React Renders — The Mental Model
Understanding why this breaks requires knowing what React does when state changes:
- You call the setter — e.g.
setCount(1) - React re-calls your component function —
Counter()runs again from the top - A new JSX tree is returned — describing what the UI should look like now
- React diffs old tree vs. new tree — and patches only the changed DOM nodes
A let count = 0 at the top of the function is re-executed in step 2, resetting it to 0 every time. The variable does change in memory when you do count++, but React never knows — it has no way to detect that a plain variable changed, so it never triggers step 1.
⚠️ OOP Instinct That Will Hurt You
In C++, you control when member functions execute. In React, you don’t control when your component function runs — React calls it whenever state changes. This means your component must be a pure function of its props and state, with no side effects.
Another instinct that hurts: in C++, vec.push_back(item) modifies the vector in-place and that is perfectly fine. In React, items.push(item) does not trigger a re-render because React compares state by reference equality (===). The array reference hasn’t changed, so React thinks nothing happened. You must create a new array: setItems([...items, item]).
React provides useState to give your component persistent memory:
function Counter() {
// SUB-GOAL: Declare state with an initial value
const [count, setCount] = React.useState(0);
// SUB-GOAL: Define the UI as a function of current state
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
React.useState(initialValue) returns a pair: the current value, and a setter function. Calling the setter triggers a re-render with the new value.
Event Handlers in React
The onClick in the counter example above is an event handler prop. In C++, you might register a callback with button.setCallback(handleClick). In React, you pass a function directly as a JSX prop:
// C++: button.setCallback(handleClick);
// Python: button.on_click = handle_click
// React — pass a function reference:
<button onClick={handleClick}>Click me</button>
// Or use an inline arrow function:
<button onClick={() => setCount(count + 1)}>+1</button>
Two key details:
- Use camelCase event names:
onClick,onChange,onSubmit(notonclick) - Pass a function reference, not a function call:
onClick={handleClick}is correct;onClick={handleClick()}calls the function immediately during render, which is almost never what you want
Rules of Hooks (important!)
- Only call hooks at the top level — never inside
if,for, or nested functions - Only call hooks from React components — not from regular JS functions
Going Deeper — Closures and Batching
The two patterns below come up frequently in real React code and will appear in later quizzes. Read through them now — even if you don’t need them for the current task.
⚠️ Watch Out: Stale Closures
When you write an arrow function inside a component, it captures the current value of variables — just like a C++ lambda with [count] captures by value. If state changes between when the function was created and when it runs, the captured value is stale:
// BUG — both timeouts capture count = 0 at render time
setTimeout(() => setCount(count + 1), 1000); // sets to 1
setTimeout(() => setCount(count + 1), 2000); // also sets to 1 (not 2!)
// FIX — functional update always receives the latest value
setTimeout(() => setCount(prev => prev + 1), 1000); // 0 → 1
setTimeout(() => setCount(prev => prev + 1), 2000); // 1 → 2 ✓
Rule of thumb: Use setCount(prev => prev + 1) (functional form) whenever the new value depends on the old value. Use setCount(5) (direct form) when you know the exact new value.
⚠️ State Updates Are Batched
React does not re-render between setter calls in the same event handler. It batches them and re-renders once at the end. This means multiple direct calls see the same stale value:
function handleTripleClick() {
setCount(count + 1); // count is 0 → sets to 1
setCount(count + 1); // count is still 0 → sets to 1 again!
setCount(count + 1); // count is still 0 → sets to 1 again!
// Result: count goes from 0 to 1, not 0 to 3
}
The functional form fixes this because each call receives the latest pending value, not the stale render-time value:
function handleTripleClick() {
setCount(prev => prev + 1); // 0 → 1
setCount(prev => prev + 1); // 1 → 2
setCount(prev => prev + 1); // 2 → 3 ✓
}
Can You Beat the Renderer?
Look at the broken counter code. Predict: when you click the +1 button, does count actually change in memory? If so, why doesn’t the display update? Write your hypothesis before reading the explanation above.
Task: Fix the Broken Counter
The counter below has two bugs:
- It uses a regular
letvariable instead ofuseState - It tries to mutate the variable directly — React won’t re-render
Can you beat the renderer? Do these ONE AT A TIME — run tests after each:
- Fix the counter: Replace
let count = 0withReact.useState(0)and use the setter in the click handler - Verify: Click +1 — does the number update? If not, check that you’re calling the setter function, not doing
count = count + 1 - Add a “Reset” button that sets the count back to
0 - Add a “−1” button that decrements the count (don’t let it go below 0)
🔍 Debugging Tip
When something doesn’t update, add a console.log at the top of your component function (before the return):
function Counter() {
const [count, setCount] = React.useState(0);
console.log('Counter rendered, count =', count); // ← appears in browser console on every render
...
}
If the log never appears after a click, the state setter was never called. If it appears but shows the wrong value, check for stale closures. The browser’s React DevTools extension also lets you inspect component state live.
const { Button } = ReactBootstrap;
function Counter() {
// BUG: Using a regular variable — React won't re-render when this changes
let count = 0;
function increment() {
count = count + 1; // BUG: Mutating a local variable has no effect on the UI
console.log('count is now', count); // This logs, but the display never updates!
}
return (
<div className="p-4 text-center">
<h2 className="display-1 mb-4">{count}</h2>
<div className="d-flex gap-2 justify-content-center">
<Button variant="primary" size="lg" onClick={increment}>
+1
</Button>
{/* Task: Add a −1 button and a Reset button */}
</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Counter />);
useState — Knowledge Check
Min. score: 80%
1. Why doesn’t let count = 0; count++; cause the UI to update in React?
React knows nothing about your local variables. The only way to trigger a re-render is to call a state setter from useState. React’s model: setter called → new state value → component function re-executed with new value → DOM diffed and patched. A bare count++ is invisible to React.
2. What is wrong with this code? if (isLoggedIn) { const [user, setUser] = React.useState(null); }
React identifies hooks by their call order, not by name. Every render must call hooks in exactly the same order. If you conditionally call useState, the order changes between renders, and React’s internal array of hook values gets misaligned — causing subtle, hard-to-debug crashes. Always call hooks unconditionally at the top of your component.
3. You have const [items, setItems] = React.useState([]). How do you correctly add an item?
React uses reference equality to detect state changes — if you mutate an array in-place (push, splice) and pass the same reference to setItems, React sees no change and skips the re-render. Always create a new array: [...items, newItem] (append), items.filter(...) (remove), items.map(...) (transform).
4. A teammate proposes storing the counter value in a global variable outside the component instead of using useState, arguing “it’s simpler and doesn’t reset.”
Evaluate this approach — what breaks?
Two problems: (1) React doesn’t know about the global variable, so changing it doesn’t trigger a re-render. (2) Global state is shared across ALL instances of the component — if you render two <Counter /> components, they’d share the same counter. useState is per-instance and triggers re-renders. This is the same reason C++ classes use member variables, not global variables.
5. (Interleaving — Which concept applies?)
For each scenario, identify the React concept needed:
(a) A greeting card that shows different names for different users
(b) A like counter that tracks clicks
(c) A heading that uses class instead of className
(a) Showing different data for different users = props — the parent passes name to the card.
(b) Tracking clicks that change over time = state (useState) — clicks are user-initiated changes.
(c) class vs className = JSX rules — JSX uses className because class is reserved in JS.
This question forces you to discriminate between the three concepts rather than recall one in isolation.
6. (Spaced review — Step 2: JSX) A student’s component renders but looks wrong: the heading has no CSS class applied, clicking does nothing, and the image tag causes a syntax error. Which combination of JSX rules is being violated?
Three different JSX rules from Step 2: (1) class → className (reserved keyword),
(2) onclick → onClick (camelCase event handlers), (3) <img> → <img /> (self-closing
tags required in JSX). This question tests whether you can diagnose which rules apply
to specific symptoms — not just recall the rules in isolation.
Lists & Keys — Rendering Collections
Learning objective: After this step you will be able to render arrays as lists using
.map()and explain why stablekeyprops are essential for React’s reconciliation.
A Note on .map() — A New Way to Loop
If you have always used for loops to iterate over arrays (as in C++ and Python), the .map() pattern will feel unfamiliar at first. You might think: “Why can’t I just use a for loop?” You can — but .map() produces a new array without mutating the original, which is exactly what React needs. Give it a few tries and it will click.
JavaScript Array Methods — Quick Reference
This step and the next use three JavaScript array methods heavily. If any are unfamiliar, review them here before continuing:
| Method | What it does | Example |
|---|---|---|
.map(fn) |
Transforms each element, returns a new array | [1,2,3].map(x => x * 2) → [2,4,6] |
.filter(fn) |
Keeps elements where fn returns true |
[1,2,3].filter(x => x > 1) → [2,3] |
.reduce(fn, init) |
Combines all elements into one value | [1,2,3].reduce((sum, x) => sum + x, 0) → 6 |
All three return new arrays (or values) — they never mutate the original. This is exactly the pattern React needs.
From for Loops to .map()
In C++ you’d render a list with a for loop. In React, you use JavaScript’s .map() to transform a data array into an array of JSX elements:
// C++:
for (const auto& task : tasks) { renderTask(task); }
// React:
// SUB-GOAL: Transform data array into JSX array
const taskElements = tasks.map(task =>
<ListGroup.Item key={task.id}>{task.text}</ListGroup.Item>
);
// SUB-GOAL: Render the array inside a container
return <ListGroup>{taskElements}</ListGroup>;
The key Prop — React’s Reconciliation Hint
When React re-renders a list, it needs to know which items are stable, added, or removed. Without keys, it compares by position — which causes unnecessary re-renders and subtle UI bugs (like inputs losing focus).
Think of key as a stable identifier, similar to a pointer address or a database primary key:
| Scenario | Without key |
With stable key |
|---|---|---|
| Insert item at start | React re-renders ALL items | React inserts only the new one |
| Delete middle item | Items after the gap get wrong state | React removes only the deleted item |
| Reorder items | State mismatches (e.g. checked checkboxes shift) | Each item keeps its own state |
Never use array index as a key for dynamic lists. If items are reordered or removed, the index changes — defeating the purpose. Use a stable, unique ID.
Can You Beat the Renderer?
Before implementing: imagine a list of 3 checkboxes where each has its own checked state. You check the middle one, then delete it. With index-based keys, what happens to the third checkbox’s state? Think it through, then read the key table above.
Task
A task list is partially implemented. Your job:
- Replace the placeholder
<ListGroup.Item>with a.map()call over thetasksarray - Give each
<ListGroup.Item>akeyprop usingtask.id(not the index!) - Show a ✓ or ✗ icon based on
task.doneusing a ternary
Bonus round: After passing the tests, add a 7th task to the tasks array (e.g., { id: 7, text: 'Deploy to production', done: false }). Does your .map() handle it automatically without any other code changes? That is the power of data-driven rendering.
const tasks = [
{ id: 1, text: 'Set up dark mode on literally everything', done: true },
{ id: 2, text: 'Star mass GitHub repos to read later', done: true },
{ id: 3, text: 'Survive a 3-hour lab without crashing', done: true },
{ id: 4, text: 'Start the side project from 3 months ago', done: false },
{ id: 5, text: 'Actually read error messages before Googling', done: false },
{ id: 6, text: 'Deploy something to production', done: false },
];
const { ListGroup } = ReactBootstrap;
function TaskList() {
return (
<div className="p-4 checklist-container">
<h2 className="h4 mb-3">After-Lecture Side Quests</h2>
<ListGroup>
{/* Task: Replace this with a .map() call over tasks */}
<ListGroup.Item>Task goes here</ListGroup.Item>
</ListGroup>
<p className="text-muted small mt-3">
{tasks.filter(t => t.done).length} / {tasks.length} complete
</p>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<TaskList />);
Lists & Keys — Knowledge Check
Min. score: 80%
1. Why is it dangerous to use the array index as a key for a dynamic list?
Keys tell React which element is which across re-renders. If item at index 2 is deleted, items at index 3, 4, 5… all shift to 2, 3, 4… React sees those keys as “the same” elements, potentially mismatching stateful inputs (like checked checkboxes or text fields) with the wrong items. Use a stable, unique ID from your data source.
2. You need to render a list of user cards. Which key strategy is correct?
Use key={user.id} — a stable, unique identifier from the data. Avoid: index (breaks with reordering/deletion), Math.random() (changes every render, forcing unmount/remount), and object references (React uses string comparison).
3. (Spaced review — Step 3: Props)
A TaskItem component needs to let the user mark a task as done. The task data comes from the parent via props. Which approach is correct?
Props are read-only — mutating them breaks one-way data flow (option A).
Duplicating props into state (option B) creates a sync risk — the two copies diverge.
Direct DOM manipulation (option D) bypasses React entirely.
The correct pattern: the child calls a callback prop (onToggle), the parent updates
state, and React re-renders with new props. This combines props (Step 3), state (Step 4),
and one-way data flow into a single decision.
4. Arrange the lines to render a playlist using .map() with stable keys.
(arrange in order)
function Playlist({ songs }) {return ({songs.map(song =>- {song.title}
)});}
- {song.title}
{songs.forEach(song =>
.map() transforms each element and returns a new array — .forEach() returns undefined, so React would render nothing. key={song.id} uses a stable identifier; key={index} breaks when items are reordered or deleted (the distractor). Each mapped element MUST have a unique key.
Conditional Rendering & Filtering
Learning objective: After this step you will be able to use conditional rendering patterns (
&&, ternary) in JSX and implement interactive list filtering withuseState.
Combining Skills — This Is the Hard Part
This step is a turning point: you are combining useState (Step 4) with .map() and .filter() (Step 5) into a single interactive component. If it feels harder than previous steps, that is because it IS harder — you are integrating multiple skills simultaneously for the first time. Take it one piece at a time: get the buttons rendering first, then wire up the filter logic.
Conditional Rendering
React uses plain JavaScript conditions inside JSX:
// SUB-GOAL: Show content only when a condition is true
{newMessages > 0 && <span className="badge">{newMessages}</span>}
// SUB-GOAL: Choose between two alternatives
{isComplete ? <span>✓ Done</span> : <span>Pending</span>}
Watch out:
{count && <Badge />}— ifcountis0, React renders the number0, not nothing! Use{count > 0 && <Badge />}instead.
Combining State and Lists — The Derived State Principle
Now you can combine useState (Step 4) with .map() (Step 5) to build interactive, filtered views. A critical principle: store the minimum state and derive everything else.
// BAD — two state variables that must stay in sync
const [allTasks, setAllTasks] = React.useState(tasks);
const [visibleTasks, setVisibleTasks] = React.useState(tasks);
// Bug: if you add a task to allTasks, visibleTasks is stale!
// GOOD — one state variable; visibleTasks is computed fresh every render
const [filter, setFilter] = React.useState('all');
const visibleTasks = allTasks.filter(t => filter === 'all' || t.status === filter);
The good version has a single source of truth (filter). visibleTasks is not state — it is a value derived from state on every render. This eliminates an entire class of sync bugs.
Here is a more complete example:
function FilteredList() {
// SUB-GOAL: Track the current filter in state
const [filter, setFilter] = React.useState('all');
// SUB-GOAL: Derive visible items from data + filter state
const visible = items.filter(item => {
if (filter === 'active') return !item.done;
if (filter === 'done') return item.done;
return true; // 'all'
});
// SUB-GOAL: Render filter controls and filtered list
return (
<div>
<ButtonGroup>
<Button onClick={() => setFilter('all')}>All</Button>
<Button onClick={() => setFilter('done')}>Done</Button>
</ButtonGroup>
<ListGroup>
{visible.map(item =>
<ListGroup.Item key={item.id}>{item.text}</ListGroup.Item>
)}
</ListGroup>
</div>
);
}
Can You Beat the Renderer?
Before implementing, predict: if filter state is 'done', which tasks from the data array should be visible? How many items will the .filter() call return?
Task
Add filter functionality to the task list from the previous step:
- Add three
<Button>components inside the<ButtonGroup>: “All”, “Active”, “Done” - Use
useStateto track the current filter - Filter the tasks array based on the selected filter
- Highlight the active filter button using react-bootstrap’s
variantprop (e.g.variant="primary"for active,variant="outline-secondary"for inactive)
const initialTasks = [
{ id: 1, text: 'Set up dark mode on literally everything', done: true },
{ id: 2, text: 'Star mass GitHub repos to read later', done: true },
{ id: 3, text: 'Survive a 3-hour lab without crashing', done: true },
{ id: 4, text: 'Start the side project from 3 months ago', done: false },
{ id: 5, text: 'Actually read error messages before Googling', done: false },
{ id: 6, text: 'Deploy something to production', done: false },
];
const { Button, ButtonGroup, ListGroup } = ReactBootstrap;
function TaskList() {
const [filter, setFilter] = React.useState('all');
// Task: Filter tasks based on the current filter state
const visibleTasks = initialTasks; // Replace with filtered list
return (
<div className="p-4 checklist-container">
<h2 className="h4 mb-3">After-Lecture Side Quests</h2>
{/* Task: Add filter buttons — "All", "Active", "Done" */}
<ButtonGroup className="mb-3">
{/* Your filter buttons here */}
</ButtonGroup>
<ListGroup>
{visibleTasks.map(task => (
<ListGroup.Item key={task.id}>
{task.done ? '✓' : '✗'} {task.text}
</ListGroup.Item>
))}
</ListGroup>
<p className="text-muted small mt-3">
{initialTasks.filter(t => t.done).length} / {initialTasks.length} complete
</p>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<TaskList />);
Conditional Rendering & Filtering — Knowledge Check
Min. score: 80%
1. What does {showBadge && <Badge />} render when showBadge is false?
React ignores false, null, undefined, and true — they render as nothing. {showBadge && <Badge />} works because when showBadge is false, JS short-circuits to false, which React ignores.
2. Analyze this bug: {count && <Badge count={count} />}. When count is 0, a 0 appears in the UI instead of nothing. Why?
JavaScript’s && returns the left operand if it’s falsy. 0 && <Badge /> evaluates to 0.
While false is not rendered by React, 0 IS rendered as the text “0”. The fix:
{count > 0 && <Badge />} — now the left operand is true or false, never 0.
3. Evaluate two approaches to implementing filters:
A: Store the full filtered array in state: const [visibleTasks, setVisibleTasks] = useState(allTasks)
B: Store only the filter string in state: const [filter, setFilter] = useState('all') and derive visible tasks with .filter()
Which is better?
React’s principle: store the minimal state and derive everything else. Storing both the
full list AND a filtered copy creates a sync risk — if items change, you must remember to
update both. With approach B, visibleTasks is always computed fresh from the source of truth.
4. Arrange the fragments to write a filter button that highlights when active, using a ternary for the variant prop. (arrange in order)
? 'primary': 'outline-secondary'}onClick={() => setFilter('done')}>Done
onClick={setFilter('done')}>
The ternary filter === 'done' ? 'primary' : 'outline-secondary' switches the button’s style based on the current filter state. The distractor onClick={setFilter('done')} calls setFilter immediately during render (because of the ()) instead of creating a function that calls it on click — a classic React bug.
5. (Interleaving — Which concept applies?) A teammate’s code has a bug: the filter buttons work correctly, but clicking ‘Add to Cart’ doesn’t update the cart count. Which concept is MOST LIKELY the problem?
If filter buttons work, state and re-rendering are functional for the filter.
But if the cart count never updates, the cart data isn’t triggering re-renders — the
most likely cause is using a plain variable instead of useState. This requires
discriminating between a state problem (Step 4), a key problem (Step 5), a method
problem, and a JSX syntax problem (Step 2) — interleaving across all prior concepts.
6. (Spaced review — Step 4: useState) A shopping cart component has this handler:
function handleBuyTwo() {
setCart([...cart, product]);
setCart([...cart, product]);
}
React batches state updates within the same event handler. Both setCart calls capture
the same cart reference (length 1), so both compute [...cart, product] → length 2.
The second call overwrites the first. Fix: use the functional form
setCart(prev => [...prev, product]) — each call receives the latest pending value.
This combines the batching concept (Step 4) with the immutable array update pattern.
7. (Spaced review — Step 1: Declarative vs Imperative)
A counter component needs to display the count and update when clicked. A student proposes three approaches. Which is correct?
A: document.getElementById('count').textContent = newCount
B: const [count, setCount] = useState(0); return <p>{count}</p>;
C: let count = 0; return <p>{count}</p>; with count++ on click
This question combines three concepts: (A) direct DOM manipulation bypasses React’s
declarative model (Step 1); (C) plain variables reset on every render and don’t trigger
re-renders (Step 4); (B) useState is the correct pattern — it persists across renders
and triggers re-rendering. The student must discriminate between declarative vs. imperative
(Step 1) AND state vs. plain variables (Step 4).
Composition — Thinking in React
Learning objective: After this step you will be able to use the
childrenprop for composition, and apply the “Thinking in React” methodology to decompose a UI.
Thinking in React
Putting the Pieces Together
This step asks you to combine everything you have learned into a structured design process. It is normal to feel overwhelmed by the number of moving parts — components, props, state, lists, conditionals. Take it one step at a time: start with a static version (no state), then add interactivity piece by piece.
React’s official methodology for approaching a new UI:
- Break the UI into a component hierarchy — each component does one job (single-responsibility principle from your OOP courses)
- Build a static version first — no state, just props
- Identify where state lives — the smallest ancestor that owns the data
- Add inverse data flow — children call functions passed as props to notify parents
Composition over Inheritance
In C++ and Java, you used inheritance (class Dog : Animal) to reuse code. React uses composition — you build complex UIs by combining small, generic components:
// SUB-GOAL: Define a generic container component
function Card({ children, className }) {
return <div className={'card ' + (className || '')}>{children}</div>;
}
// SUB-GOAL: Compose specific UI by nesting inside the container
function ProfileCard({ user }) {
return (
<Card className="profile">
<Avatar src={user.avatar} />
<h3>{user.name}</h3>
</Card>
);
}
The children prop lets any content be nested inside a component, making it a composable container — analogous to C++ templates or Python’s *args.
Lifting State Up
When two sibling components need the same data, move the state to their lowest common ancestor and pass it down as props. The child notifies the parent via a callback prop:
function Parent() {
const [text, setText] = React.useState('');
return (
<>
<SearchBar value={text} onChange={setText} />
<ResultsList filter={text} />
</>
);
}
⚠️ Prop Drilling
As your component tree grows, you may find yourself passing a prop through several intermediate components that don’t use it — just so a deeply nested child can access it. This is called prop drilling:
App → Profile → Sidebar → UserCard (only UserCard uses the `user` prop)
Prop drilling is not a bug, but it makes code harder to maintain. If you are drilling more than 2-3 levels, consider React’s Context API (not covered in this tutorial) to share data without threading it through every layer.
Multiple Files — How They Connect
This is the first step with three separate files (Avatar.jsx, StatBadge.jsx, App.jsx). In a real React project, each component lives in its own file and you use import/export to connect them. In this tutorial, all files are loaded into the same page automatically — so App.jsx can use Avatar and StatBadge without any imports. Just define the component in its file and use it by name in another file.
Can You Beat the Renderer?
Before writing any code, look at the user data in App. Predict: how many components do you need? Which component should accept children? Which should receive individual props like label and value? Sketch a component tree on paper (or in your head), then compare with the specification below.
Task: Build a GitHub-style Profile Page
Implement the component structure below. The specification is intentionally open-ended — there is no “correct” visual design.
Specification:
Avatar: Renders a circular image (use the providedavatarUrl) and the user’susernameStatBadge: Shows alabeland avalueside by side (e.g. “Repos 42”)ProfileCard: UsesAvatarand threeStatBadgecomponents to build the full cardApp: Renders twoProfileCardcomponents with the provided user data
Connection to
children: When you nestAvatarandStatBadgeinside<Card.Body>, you are usingchildrenin action — Bootstrap’sCard.Bodyrenders whatever is placed between its tags. Your own components can do the same.
Bonus round 1: After passing the tests, add a third user to the users array in App. Does your component hierarchy display the new card without any changes to Avatar, StatBadge, or ProfileCard? If yes, your composition is working — the same components render any number of users.
Bonus round 2: Extract a reusable StatsRow component that accepts children and wraps them in a flex container (<div className="d-flex justify-content-around">). Use it inside ProfileCard to wrap the three StatBadge components. This directly practices the children prop pattern from the Composition section above.
// Task: Implement Avatar
// Props: avatarUrl (string), username (string)
// Should render a circular image and the username text
function Avatar({ avatarUrl, username }) {
return (
<div>
{/* Your implementation */}
</div>
);
}
// Task: Implement StatBadge
// Props: label (string), value (number)
// Should show the label and value — e.g. "Repos 42"
function StatBadge({ label, value }) {
return (
<div>
{/* Your implementation */}
</div>
);
}
const { Card } = ReactBootstrap;
// Task: Implement ProfileCard using Avatar and StatBadge
// Props: user object with: name, username, avatarUrl, repos, followers, following
function ProfileCard({ user }) {
return (
<Card className="shadow-sm profile-card">
<Card.Body>
{/* Task: Use Avatar and StatBadge here */}
</Card.Body>
</Card>
);
}
function App() {
const users = [
{
name: 'Margaret Hamilton',
username: 'margaret-hamilton',
avatarUrl: '/img/hamilton.png',
repos: 15, followers: 4096, following: 12
},
{
name: 'Fred Brooks',
username: 'fred-brooks',
avatarUrl: '/img/brooks.png',
repos: 7, followers: 1024, following: 300
},
{
name: 'Barbara Liskov',
username: 'barbara-liskov',
avatarUrl: '/img/liskov.png',
repos: 12, followers: 2048, following: 64
},
{
name: 'David Parnas',
username: 'david-parnas',
avatarUrl: '/img/parnas.png',
repos: 9, followers: 512, following: 8
},
];
return (
<div className="p-4 d-flex gap-4 flex-wrap bg-light min-vh-100">
{users.map(user => (
<ProfileCard key={user.username} user={user} />
))}
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Composition — Knowledge Check
Min. score: 80%1. React favors composition over inheritance. Which statement best explains why?
Deep inheritance chains make it hard to understand or change one level without breaking another. React’s component model encourages building a Dialog from a generic Card, passing specific content as children — rather than creating a DialogCard extends Card hierarchy.
2. What does the children prop give you?
children is an implicit prop containing whatever JSX is placed between <MyComponent> and </MyComponent>. This is the foundation of composable container components.
3. A <SearchBar> and <ProductTable> are siblings. The user types in SearchBar and the table should filter. Where should filterText state live?
Lifting state up: state belongs in the lowest common ancestor of all components that need it. SearchBar receives filterText as a prop and calls onFilterChange(e.target.value) on input. The parent updates state, triggering a re-render of both.
4. A <UserCard> needs a user prop from a grandparent, passing through <Profile> which doesn’t use it. What is this antipattern called?
Prop drilling occurs when you pass props through layers of components that don’t use them. Solutions: React Context API (for widely-shared state) or state management libraries. Rule of thumb: if drilling more than 2-3 levels, reconsider.
5. (Spaced review — Step 5: Lists & Keys)
A drag-and-drop todo list lets users reorder items. Each item has a text input for editing. The current code uses key={index}. A user drags item C from position 3 to position 1. What happens to the text typed into item A’s input field?
With index-based keys, React identifies components by position. After reordering,
position 0 is now item C, but React thinks it is still “the same component” (key=0) —
so it keeps item A’s old input state and pairs it with item C’s text. This is why stable
IDs (key={task.id}) are essential for dynamic lists. This tests the consequence
of bad keys in a realistic scenario, not just the rule.
Integration Project: Build a Mini Store
Learning objective: After this step you will be able to design and implement a complete React application that integrates components, props, state, lists, conditional rendering, and composition.
The Training Wheels Are Off
In Steps 1-7 you had scaffolding: pre-built component signatures, provided data, and step-by-step task lists. This step has none of that. You decide the component hierarchy, where state lives, and how data flows. No hints, no scaffolding — just you and everything you’ve learned.
If you feel uncertain, that’s actually a good sign: you know enough to see the design space, but haven’t yet internalized the patterns through unguided practice. That’s exactly what this step is for. Every professional React developer went through this exact transition — from “I can follow tutorials” to “I can build from scratch.” It is supposed to feel like a stretch. Work through “Thinking in React” below — it’ll guide your decisions.
Requirements
Build a mini product store with the following features:
- Product list: Display all products from the provided data using
.map()with properkeyprops - Product card component: Each product shows its name, price (formatted), category, and an “Add to Cart” button. Show a “Sale!” badge if
onSaleis true - Shopping cart: Display the number of items in the cart. Use
useStateto track cart items - Category filter: Add buttons to filter products by category (“All”, “Tech”, “Vibes”, “Music”). Use
useStatefor the active filter - Cart total: Show the total price of items in the cart
- Composition: Use at least 3 separate components (e.g.
ProductCard,CartSummary,FilterBar)
Thinking in React — Apply the Methodology
Before coding, plan your component hierarchy:
- What components do you need? (single-responsibility principle)
- Build a static version first (no state — just props)
- What is the minimal state? (filter string, cart items array)
- Where does each piece of state live? (lowest common ancestor)
Hints (only if stuck)
- Cart state:
const [cart, setCart] = React.useState([]) - Add to cart:
setCart([...cart, product]) - Total:
cart.reduce((sum, item) => sum + item.price, 0).toFixed(2) - Filter: same pattern as Step 6
Defensive Coding Tip
Real-world data is messy. What if a product’s price is undefined or a string? You can guard against this with default values and optional chaining:
// Default value — if price is missing, show 0.00
<p>${(price ?? 0).toFixed(2)}</p>
// Optional chaining — safely access nested properties
<p>{product?.category}</p>
You do not need these for the tests (the data is clean), but they are essential habits for production code.
// Integration Project: Build a mini product store.
// No scaffolding — apply everything you have learned.
// Available: ReactBootstrap.Card, .Button, .Badge, .ButtonGroup, .ListGroup, etc.
const { Card, Button, Badge, ButtonGroup } = ReactBootstrap;
const products = [
{ id: 1, name: 'Lo-Fi Study Beats Vinyl', price: 29.99, category: 'Music', onSale: false },
{ id: 2, name: 'Mechanical Keyboard', price: 89.99, category: 'Tech', onSale: true },
{ id: 3, name: 'Desk LED Strip', price: 19.99, category: 'Tech', onSale: false },
{ id: 4, name: 'Anime Desk Mat', price: 24.99, category: 'Vibes', onSale: true },
{ id: 5, name: 'Matcha Starter Kit', price: 34.99, category: 'Vibes', onSale: false },
{ id: 6, name: 'Cloud Earbuds', price: 45.99, category: 'Tech', onSale: false },
];
// Build your components and App here
function App() {
return (
<div className="p-4">
<h1 className="h2 mb-4">Mini Store</h1>
{/* Your implementation */}
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Final Assessment — Everything You've Learned
Min. score: 80%1. Evaluate this code for a mini store. What are the bugs?
function App() {
let cart = [];
const addToCart = (product) => { cart.push(product); };
return (
<div>
{products.map(p => <ProductCard product={p} onAdd={addToCart} />)}
<p>Cart: {cart.length} items</p>
</div>
);
}
(1) let cart = [] resets on every render and .push() mutates in-place without triggering a re-render. Fix: const [cart, setCart] = React.useState([]) and setCart([...cart, product]).
(2) Each mapped element needs a key prop: <ProductCard key={p.id} .../>.
2. Analyze the component design of a store app. A student puts product rendering, cart management, filtering, and the total calculation ALL in the App component. What is wrong with this approach?
Single-responsibility applies to components just as it does to C++ classes. A ProductCard
can be tested and reused independently. A CartSummary can be modified without touching
product display logic. This is Step 1 of “Thinking in React”: decompose the UI into a
component hierarchy where each component does one job.
3. In the mini store, both ProductCard (to show “In Cart” status) and CartSummary (to show the total) need access to the cart array. Where should cart state live?
Lifting state up: the cart state belongs in the lowest common ancestor of all components
that need it. App owns [cart, setCart], passes cart to CartSummary and an
onAdd callback to ProductCard. This is the same “Thinking in React” pattern from Step 7.
4. Predict what renders after clicking the button:
function App() {
const [items, setItems] = React.useState(['A', 'B']);
const add = () => { items.push('C'); setItems(items); };
return <><button onClick={add}>Add</button><p>{items.join(', ')}</p></>;
}
React uses reference equality (===) to detect state changes. items.push('C') mutates the
existing array in-place — the reference stays the same. When you call setItems(items), React
compares oldRef === newRef and sees no change, so it skips the re-render. The fix:
setItems([...items, 'C']) — the spread creates a NEW array with a different reference.
This combines the immutability principle (Step 4) with array operations (Step 5).
5. A product card should show “In Cart” only when the product is already in the cart array. Which JSX pattern is correct?
&& short-circuit is the correct pattern for show/hide. Use .filter() with item.id === product.id
to check membership by ID rather than object reference (reference equality on objects is unreliable
after state updates create new arrays — [...cart, product] creates a new array, so cart.includes(product) may fail).
Option A is invalid syntax — ternary requires both branches (? ... : ...). Option C is invalid — if statements
cannot appear inside JSX expression braces (JSX only accepts expressions, not statements).
6. (Comprehensive review — Step 1: Declarative Paradigm)
A teammate suggests using document.getElementById('counter').textContent = newCount inside a React component to update the display. What happens?
React’s declarative model means React owns the DOM. When state changes, React re-renders the component and replaces the DOM content with what the JSX describes. Any manual DOM changes are overwritten. This is why you update state, not the DOM — React handles the DOM for you.
7. (Comprehensive review — Step 2: JSX)
A component renders but the event handler never fires: <button onclick={() => setCount(count + 1)}>Click</button>. The button appears but clicking does nothing. What is wrong?
JSX event handlers use camelCase: onClick, onChange, onSubmit. The lowercase HTML
attribute onclick is not recognized by React and is silently ignored, so the button renders
but never responds to clicks. This is a very common “why doesn’t my button work?” bug.
8. (Comprehensive review — Step 3: Props)
A ProfileCard component accepts user as a prop. Inside it, you write user.name = 'Anonymous' to hide the real name. What is the problem?
Props are passed by reference in JavaScript. Mutating user.name changes the original object
the parent holds, which can corrupt data across the entire app. Props must be treated as
read-only — if you need to transform data, create a local variable:
const displayName = user.name || 'Anonymous'.
9. (Comprehensive review — Step 4: useState)
What is wrong with <button onClick={handleClick()}>Go</button>?
handleClick() with parentheses calls the function right now, during the render pass.
This usually causes an infinite loop (if handleClick calls a setter, which re-renders,
which calls handleClick() again). Pass a reference: onClick={handleClick} or wrap
in an arrow function: onClick={() => handleClick()}.
10. (Comprehensive review — Step 5: Keys)
Two separate <ul> lists in the same component both have items with key="1", key="2", etc. Does this cause a problem?
Keys only need to be unique among siblings — within the same .map() call or parent element.
Two separate <ul> lists can both have items with key="1". React scopes key comparisons
to each parent, not globally.
11. (Comprehensive review — Step 7: Composition)
You need a WarningDialog and an InfoDialog that share the same layout (title bar, close button, body area) but show different content. Which approach is most aligned with React’s philosophy?
React favors composition over inheritance. A generic Dialog component with children lets
you compose any specific dialog: <Dialog variant="warning"><p>Be careful!</p></Dialog>.
This avoids the fragile base class problem of inheritance and the maintenance burden of copy-paste.
12. (Comprehensive review — Design challenge) You are building a playlist app. Users can add songs, remove songs, and filter by genre. Which correctly identifies the minimal state?
Store the minimum state: songs (the source of truth) and selectedGenre (the user’s
current filter choice). filteredSongs is derived — computed as
songs.filter(s => selectedGenre === 'All' || s.genre === selectedGenre) on every render.
songCount is just songs.length. Storing derived data in state creates sync bugs.
You Made It!
You Built a React App From Scratch
Take a moment to appreciate what you just did. You walked into this tutorial knowing C++ and Python. You are walking out with a working knowledge of React and modern declarative UI development.
Here is everything you learned:
The Declarative Paradigm (Step 1)
- The fundamental shift: describe what the UI should look like, not how to update it
- React’s mental model: UI = f(state) — your component is a function from data to UI
- The Virtual DOM: React diffs old and new trees and patches only what changed
Components & JSX (Step 2)
- Components are functions that return UI — React’s fundamental building block
- JSX is JavaScript, not HTML:
className, self-closing tags, camelCase events, single root - Babel compiles JSX to
React.createElement()calls — it is syntactic sugar, not magic
Props — Data Flowing Down (Step 3)
- Props are function arguments for components — they parameterize behavior
- Props are read-only: never mutate them inside a child component
- Destructuring unpacks props cleanly:
function Card({ title, price }) { ... } - Conditional rendering with
&&: show UI only when a condition is true
State — Making Components Remember (Step 4)
useStategives components persistent memory that survives re-renders- Calling the setter triggers a re-render — plain variables do not
- State updates are immutable: create new arrays/objects with spread (
...), never mutate in place - The functional update form (
setCount(prev => prev + 1)) avoids stale closures
Lists & Keys (Step 5)
.map()transforms data arrays into JSX arrays — React’s list rendering patternkeyprops tell React which items are stable across re-renders- Never use array index as a key for dynamic lists — use stable IDs from your data
Conditional Rendering & Filtering (Step 6)
&&for show/hide, ternary for either/or — both are JSX expression patterns- Store minimal state, derive everything else:
visibleItems = items.filter(...) - Watch out:
{0 && <Component />}renders0, not nothing — use{count > 0 && ...}
Composition — Thinking in React (Step 7)
- Composition over inheritance: build complex UIs from small, generic components
- The
childrenprop makes components into flexible containers - Lifting state up: shared state belongs in the lowest common ancestor
- The “Thinking in React” methodology: decompose → static version → add state → add data flow
Full Integration (Step 8)
- You designed and built a complete React app with zero scaffolding
- You chose the component hierarchy, decided where state lives, and wired up data flow
- You combined every skill: components, props, state, lists, keys, filtering, composition
What Comes Next
You now have the foundation to build real React applications. Here are natural next steps:
- useEffect — Side effects like API calls, timers, and event listeners
- React Router — Multi-page navigation in single-page apps
- Context API — Sharing state without prop drilling
- Custom Hooks — Extracting reusable stateful logic
- TypeScript + React — Type safety for props and state (your C++ instincts will love this)
- Testing — React Testing Library for component tests
One Last Thing
Remember Step 4, when a regular variable didn’t update the UI and everything felt broken? You got past that. Remember Step 8, when the scaffolding disappeared and you had to design everything yourself? You built it anyway.
Every concept that felt confusing at first — JSX syntax, the declarative paradigm, immutable state updates — is now a tool in your kit. The next time something in React doesn’t click immediately, remember: you have already proven you can push through the confusion and come out the other side.
Now go build something.