1

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 structurewhat 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:

  1. In App.jsx: Change "World" to another name in the name variable
  2. In styles.css: Change the color from tomato to #2774AE (or any other color)

The preview rebuilds automatically when you save (Ctrl+S). Use ↻ Refresh if needed.

Starter files
step1/styles.css
.greeting {
  color: tomato;        /* Task 2: Change this color */
}
step1/App.jsx
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 />);
2

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

  1. Find and fix all three JSX bugs in App.jsx (hint: use the table above)
  2. 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.

Starter files
step2/App.jsx
// 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 />);
3

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 const function 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:

  1. Displays the product name as an <h3>
  2. Displays the price formatted to two decimal places (use price.toFixed(2))
  3. Displays the description in a <p> tag
  4. Shows a “Sale!” badge only when onSale is true

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.

Starter files
step3/App.jsx
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 />);
4

useState — Making Components Remember

Learning objective: After this step you will be able to implement interactive components using useState and 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:

  1. You call the setter — e.g. setCount(1)
  2. React re-calls your component functionCounter() runs again from the top
  3. A new JSX tree is returned — describing what the UI should look like now
  4. 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 (not onclick)
  • 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!)

  1. Only call hooks at the top level — never inside if, for, or nested functions
  2. 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:

  1. It uses a regular let variable instead of useState
  2. 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:

  1. Fix the counter: Replace let count = 0 with React.useState(0) and use the setter in the click handler
  2. Verify: Click +1 — does the number update? If not, check that you’re calling the setter function, not doing count = count + 1
  3. Add a “Reset” button that sets the count back to 0
  4. 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.

Starter files
step4/App.jsx
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 />);
5

Lists & Keys — Rendering Collections

Learning objective: After this step you will be able to render arrays as lists using .map() and explain why stable key props 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:

  1. Replace the placeholder <ListGroup.Item> with a .map() call over the tasks array
  2. Give each <ListGroup.Item> a key prop using task.id (not the index!)
  3. Show a ✓ or ✗ icon based on task.done using 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.

Starter files
step5/App.jsx
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 />);
6

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

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 />} — if count is 0, React renders the number 0, 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:

  1. Add three <Button> components inside the <ButtonGroup>: “All”, “Active”, “Done”
  2. Use useState to track the current filter
  3. Filter the tasks array based on the selected filter
  4. Highlight the active filter button using react-bootstrap’s variant prop (e.g. variant="primary" for active, variant="outline-secondary" for inactive)
Starter files
step6/App.jsx
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 />);
7

Composition — Thinking in React

Learning objective: After this step you will be able to use the children prop 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:

  1. Break the UI into a component hierarchy — each component does one job (single-responsibility principle from your OOP courses)
  2. Build a static version first — no state, just props
  3. Identify where state lives — the smallest ancestor that owns the data
  4. 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 provided avatarUrl) and the user’s username
  • StatBadge: Shows a label and a value side by side (e.g. “Repos 42”)
  • ProfileCard: Uses Avatar and three StatBadge components to build the full card
  • App: Renders two ProfileCard components with the provided user data

Connection to children: When you nest Avatar and StatBadge inside <Card.Body>, you are using children in action — Bootstrap’s Card.Body renders 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.

Starter files
step7/Avatar.jsx
// 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>
  );
}
step7/StatBadge.jsx
// 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>
  );
}
step7/App.jsx
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 />);
8

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:

  1. Product list: Display all products from the provided data using .map() with proper key props
  2. Product card component: Each product shows its name, price (formatted), category, and an “Add to Cart” button. Show a “Sale!” badge if onSale is true
  3. Shopping cart: Display the number of items in the cart. Use useState to track cart items
  4. Category filter: Add buttons to filter products by category (“All”, “Tech”, “Vibes”, “Music”). Use useState for the active filter
  5. Cart total: Show the total price of items in the cart
  6. Composition: Use at least 3 separate components (e.g. ProductCard, CartSummary, FilterBar)

Thinking in React — Apply the Methodology

Before coding, plan your component hierarchy:

  1. What components do you need? (single-responsibility principle)
  2. Build a static version first (no state — just props)
  3. What is the minimal state? (filter string, cart items array)
  4. 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.

Starter files
step8/App.jsx
// 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 />);
9

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)

  • useState gives 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 pattern
  • key props 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 />} renders 0, not nothing — use {count > 0 && ...}

Composition — Thinking in React (Step 7)

  • Composition over inheritance: build complex UIs from small, generic components
  • The children prop 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.