Module 4 · React · Drills

Drills: React Fundamentals

Reading React is not knowing React. Type every component yourself, run it in the browser, and watch it re-render before you reveal the solution. The flip only sticks through reps.

How to use this page Spin up a sandbox once: npm create vite@latest react-drills → pick React + JavaScriptcd react-drillsnpm installnpm run dev. Edit src/App.jsx, drop each drill's component in, render it, and watch the dev server hot-reload. Attempt first, then click "Show solution". Tick each box; progress is saved in this browser.

A · Warm-up reps Basic

Drill 1 props

Write a <DocCard> component that takes title and pages props and renders them. Then render one with title "Intro" and 12 pages.

Show solution
function DocCard({ title, pages }) {
  return (
    <div className="card">
      <h3>{title}</h3>
      <p>{pages} pages</p>
    </div>
  );
}

// render it
<DocCard title="Intro" pages={12} />

Props are read-only inputs. Note pages={12} uses braces for a number; strings can use plain quotes.

Drill 2 useState

Build a <Counter>: a button showing a count that increases by one on each click, starting at 0.

Show solution
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

The setter triggers a re-render. count = count + 1 would change nothing on screen.

Drill 3 controlled input

Make a text <input> whose typed value is stored in state, and show a live You typed: … line below it.

Show solution
import { useState } from "react";

function Echo() {
  const [text, setText] = useState("");
  return (
    <div>
      <input
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <p>You typed: {text}</p>
    </div>
  );
}

value + onChange = controlled. State is the single source of truth; you never read the DOM to know what's typed.

B · Stretch Intermediate

Drill 4 map + key

Given docs = [{id:1,title:"a.pdf"},{id:2,title:"b.pdf"}], render them as a <ul> with a correct key on each item.

Show solution
function DocList({ docs }) {
  return (
    <ul>
      {docs.map((doc) => (
        <li key={doc.id}>{doc.title}</li>
      ))}
    </ul>
  );
}

Use the stable doc.id as the key — not the array index — so React tracks items correctly across re-renders.

Drill 5 conditional render

Add a "logged in" boolean to state and a toggle button. Show Welcome back when true and Please log in when false, using a ternary.

Show solution
import { useState } from "react";

function Status() {
  const [loggedIn, setLoggedIn] = useState(false);
  return (
    <div>
      {loggedIn ? <p>Welcome back</p> : <p>Please log in</p>}
      <button onClick={() => setLoggedIn(!loggedIn)}>
        Toggle
      </button>
    </div>
  );
}

For "show or nothing" you'd use {loggedIn && <p>…</p>} instead — but watch the falsy-0 trap with numeric conditions.

C · Build challenge Build

Mini-project Build a small doc list with add and remove. A controlled input adds a document; each item has a "×" button that removes it. State lives in the parent; this is the exact CRUD shape DocChat's UI uses everywhere.

Build · add + remove list

Start from an empty list. Adding appends a new item with a unique id; removing filters it out by id. Show "No documents yet." when empty.

Show solution
import { useState } from "react";

export default function App() {
  const [docs, setDocs] = useState([]);
  const [title, setTitle] = useState("");

  function add(e) {
    e.preventDefault();
    const clean = title.trim();
    if (!clean) return;
    setDocs([...docs, { id: Date.now(), title: clean }]);
    setTitle("");
  }

  function remove(id) {
    setDocs(docs.filter((d) => d.id !== id));
  }

  return (
    <div>
      <form onSubmit={add}>
        <input
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          placeholder="Add a document"
        />
        <button>Add</button>
      </form>

      {docs.length === 0
        ? <p>No documents yet.</p>
        : (
          <ul>
            {docs.map((doc) => (
              <li key={doc.id}>
                {doc.title}{" "}
                <button onClick={() => remove(doc.id)}>×</button>
              </li>
            ))}
          </ul>
        )}
    </div>
  );
}

Both updates return a new array: [...docs, x] to add, docs.filter(...) to remove. Never push/splice the existing array — React compares references to decide whether to re-render.

D · Rapid recall Flashcards

Click a card to flip it. Say the answer out loud before you flip — that's the rep that builds storage strength.

What is a React component?
A function (PascalCase name) that returns JSX describing some UI.
click to flip
Props vs state?
Props = read-only inputs passed from a parent. State = data a component owns and can change (triggering a re-render).
click to flip
What does useState return?
A pair: [value, setValue]. Call the setter to update — never assign the value directly.
click to flip
Why does a list need a key?
So React can match each item to its DOM node across re-renders. Use a stable id, not the index.
click to flip
What makes an input "controlled"?
Its value comes from state and onChange writes back to state — state is the single source of truth.
click to flip
className vs class in JSX?
Use classNameclass is a reserved JS word. (Also htmlFor, not for.)
click to flip

E · Self-check before moving on

Tick each only if you can do it without looking:

Next All ticked? You can describe a UI with state — the hardest mental jump in React. Next we go beyond useState to side effects and talking to your FastAPI backend: Lesson 4.2 — Hooks & Data Fetching.