Module 4 · React · Drills
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.
npm create vite@latest react-drills → pick React + JavaScript → cd react-drills → npm install → npm 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Click a card to flip it. Say the answer out loud before you flip — that's the rep that builds storage strength.
useState return?[value, setValue]. Call the setter to update — never assign the value directly.key?value comes from state and onChange writes back to state — state is the single source of truth.className vs class in JSX?className — class is a reserved JS word. (Also htmlFor, not for.)Tick each only if you can do it without looking:
useState and wire a controlled inputmap + key and conditionally renderuseState to side effects and talking to your FastAPI backend: Lesson 4.2 — Hooks & Data Fetching.