Build a working task tracker with raw JavaScript and DOM manipulation. The point is to feel the pain — by the end you'll understand exactly what React eliminates.
index.html— same shell, with<script src="script.js" defer></script>and an empty<ul>style.css— extended with.doneand.delete-btnstylesscript.js— the logic
Right-click index.html → Open with Live Server. Add tasks, tick them, delete them.
| Concept | JS | R/Python equivalent |
|---|---|---|
| Variable | const x = 5 / let x = 5 |
x <- 5 / x = 5 |
| Lambda | (a, b) => a + b |
\(a, b) a + b / lambda a, b: a + b |
| Map | arr.map(x => x * 2) |
purrr::map() / list comp |
| Filter | arr.filter(x => x > 0) |
purrr::keep() / list comp |
| Object | {name: 'A', age: 3} |
named list / dict |
| Equality | === (strict) |
== (always use === in JS, never ==) |
| Null-ish | null, undefined |
NULL, missing |
| String interp | `Hi ${name}` |
glue("Hi {name}") / f-string |
Use const by default, let only when reassigning. Never var.
Open script.js and read it top to bottom. Notice the structure:
- Grab DOM elements by ID (
document.getElementById) - Hold data in a plain JS array (
tasks) - Define a
render()function that wipes the list and rebuilds it from scratch - Wire up event handlers (
onclick,onchange) that mutate data then callrender()
Read the comments marked <-- have to remember to call this in script.js. There are four places we call render(). Forget one and the UI silently lies about your data.
Other pain:
- The data (
tasksarray) and the DOM are two separate worlds you must manually keep in sync. - We rebuild the entire list every time, even when one task changes. Inefficient for large lists.
- Event handlers attached in
render()are recreated every cycle. Easy to leak memory in real apps. - No componentisation — if you wanted a second task list elsewhere, you'd copy-paste everything.
- Remove one of the
render()calls and see what breaks. - Add a "clear completed" button. You'll need: a new button in HTML, a new event handler in JS, and yes — another
render()call. - Open devtools → Console tab. Type
tasksand hit Enter — you can inspect your data live. Typetasks.push({id: 999, text: 'hi', done: false})thenrender()to add a task from the console.
React lets you write: "given this tasks array, the UI looks like this JSX." You change the array, React figures out the minimal DOM updates. No manual render(), no getElementById, no two worlds to sync.