Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Part 4: React (40 min)

Goal

Rebuild the same task tracker in React. Same output, dramatically different code.

What's here

  • package.json — declares dependencies (React, Vite). Like DESCRIPTION / requirements.txt.
  • vite.config.js — build tool config
  • index.html — single page with one <div id="root"> where React mounts
  • src/main.jsx — entry point; mounts <App /> into #root
  • src/App.jsx — main component (state, handlers, layout)
  • src/TaskItem.jsx — child component for each list item
  • src/index.css — same look as Part 3

Run it

cd 04-react
npm install   # downloads dependencies (one-off, ~30s)
npm run dev   # starts Vite dev server on port 5173

Open the URL Vite prints. In Codespaces, click "Open in Browser" on the port forwarding popup.

The dev server has hot module replacement: save any file and the browser updates without losing state. Try it.

How React differs from Part 3

Vanilla JS (Part 3) React (Part 4)
You write Step-by-step DOM mutations "Given this state, the UI looks like this"
Data → UI sync Manual (render() calls everywhere) Automatic
Components Copy-paste HTML Reusable functions returning JSX
Style Imperative Declarative

JSX

JSX is HTML-flavoured JavaScript. Inside a component's return, you write markup; inside {} you write JS expressions.

<input value={input} onChange={e => setInput(e.target.value)} />

Gotchas:

  • className not class (because class is a reserved word in JS)
  • All event handlers are camelCase: onClick, onChange, onKeyDown
  • Self-close empty tags: <input /> not <input>
  • Only one root element per return (wrap in <div> or <>...</> fragment)

State and the golden rule

const [tasks, setTasks] = useState([])

useState returns a pair: the current value and a setter function. The destructuring [a, b] is JS array destructuring — like c(a, b) <- list(1, 2) would be in R if it worked.

The rule: never mutate state. Always pass a new value to the setter.

// WRONG — mutates the existing array, React won't re-render
tasks.push(newTask)

// RIGHT — creates a new array
setTasks([...tasks, newTask])

Same for objects:

// WRONG
task.done = !task.done

// RIGHT
setTasks(tasks.map(t => t.id === id ? { ...t, done: !t.done } : t))

The ... is the spread operator — it expands an array/object inline. Think of it as immutable-by-default, the way you'd write a dplyr pipeline that returns a new tibble rather than mutating in place.

Components and props

TaskItem is a function that takes props and returns JSX:

function TaskItem({ task, onToggle, onDelete }) { ... }

The { task, onToggle, onDelete } destructures the props object. In App.jsx we pass them as attributes:

<TaskItem task={task} onToggle={toggleTask} onDelete={deleteTask} />

Data flows down through props. Events flow up by calling callback props. That's the entire mental model.

useEffect — side effects

useEffect(() => {
  localStorage.setItem('tasks', JSON.stringify(tasks))
}, [tasks])

Runs the function after every render where tasks changed. The array [tasks] is the dependency list:

  • [tasks] — runs when tasks changes
  • [] — runs once on mount
  • omitted — runs after every render (rarely what you want)

useEffect is for talking to "the outside world" — localStorage, network requests, timers, subscriptions. Stuff that isn't pure rendering.

Read the code in this order

  1. index.html — see the empty #root div
  2. src/main.jsx — see React mount into it
  3. src/App.jsx — read top to bottom; trace how state flows
  4. src/TaskItem.jsx — see how props come in, events go out

Compare side by side with 03-vanilla-js/script.js. Same app, very different code.

Try this

  1. Add a "Clear completed" button. You'll need an onClick that calls setTasks(tasks.filter(t => !t.done)). Notice you don't have to call any render function.
  2. Show a count: <p>{tasks.filter(t => !t.done).length} remaining</p>. This re-runs every render automatically.
  3. Open React DevTools (browser extension) → Components tab. Click <App /> and watch state update live as you interact.
  4. Add an editing state and let the user double-click a task to edit its text.

React DevTools

Install the React Developer Tools browser extension. It adds two tabs to devtools (Components, Profiler) that let you inspect component state and prop flow.

Next

Part 5: Next.js