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 130!';
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 130!</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.

Predict Before You Modify

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 your own 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
styles.css
.greeting {
  color: tomato;        /* Task 2: Change this color */
}
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)

Predict Before You Code

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

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.

Predict Before You Code

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.

Challenge (optional): 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
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.

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

⚠️ 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  ✓
}

Predict Before You Fix

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

Task:

  1. Fix the counter so it actually updates the display when clicked
  2. Add a “Reset” button that sets the count back to 0
  3. 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
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.

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.

Predict Before You Code

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

Challenge (optional): 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
App.jsx
const tasks = [
  { id: 1, text: 'Set up React development environment', done: true },
  { id: 2, text: 'Learn about components and JSX',       done: true },
  { id: 3, text: 'Understand props and data flow',       done: true },
  { id: 4, text: 'Master useState for interactivity',    done: false },
  { id: 5, text: 'Render lists with .map() and keys',    done: false },
  { id: 6, text: 'Build a real React app',               done: false },
];

const { ListGroup } = ReactBootstrap;

function TaskList() {
  return (
    <div className="p-4 checklist-container">
      <h2 className="h4 mb-3">React Learning Checklist</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.

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>
  );
}

Predict Before You Code

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
App.jsx
const initialTasks = [
  { id: 1, text: 'Set up React development environment', done: true },
  { id: 2, text: 'Learn about components and JSX',       done: true },
  { id: 3, text: 'Understand props and data flow',       done: true },
  { id: 4, text: 'Master useState for interactivity',    done: false },
  { id: 5, text: 'Render lists with .map() and keys',    done: false },
  { id: 6, text: 'Build a real React app',               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">React Learning Checklist</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.

Predict Before You Build

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:

Challenge (optional): 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.

Starter files
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>
  );
}
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>
  );
}
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: 'Ada Lovelace',
      username: 'ada-lovelace',
      avatarUrl: 'https://i.pravatar.cc/80?img=47',
      repos: 12, followers: 2048, following: 64
    },
    {
      name: 'Alan Turing',
      username: 'alan-turing',
      avatarUrl: 'https://i.pravatar.cc/80?img=60',
      repos: 7, followers: 9999, following: 3
    },
  ];

  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 Come Off

In Steps 1-7 you had scaffolding: pre-built component signatures, provided data structures, and step-by-step task lists. This step has none of that. You decide the component hierarchy, where state lives, and how data flows. This is intentional — applying concepts independently is how they transfer from tutorial exercises to real projects.

If you feel uncertain, that is a sign the earlier steps did their job: you know enough to recognize the design space, but haven’t yet internalized the patterns through unguided practice. Work through the “Thinking in React” methodology below — it will 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”, “Electronics”, “Accessories”). 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)

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
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: 'Wireless Mouse',       price: 29.99,  category: 'Electronics',  onSale: false },
  { id: 2, name: 'Mechanical Keyboard',   price: 89.99,  category: 'Electronics',  onSale: true  },
  { id: 3, name: 'USB-C Hub',             price: 45.99,  category: 'Electronics',  onSale: false },
  { id: 4, name: 'Laptop Stand',          price: 34.99,  category: 'Accessories',  onSale: true  },
  { id: 5, name: 'Desk Mat',              price: 19.99,  category: 'Accessories',  onSale: false },
  { id: 6, name: 'Cable Management Kit',  price: 14.99,  category: 'Accessories',  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 />);