In the previous chapter, you built search and a full form, following rules this book handed you on faith: Always spread, never assign; derive, don’t store. The Learning Tracker works — but “works” and “understood” are different achievements.
This chapter closes that gap. You’ll watch React batch three updates into one, see exactly why mutation breaks rendering — complete with a ghost course haunting your catalog — and practice adding, removing and updating list state the immutable way.
It’s the most conceptual chapter of Part II, but everything happens in running code, and it ends with a bug hunt: three planted state mistakes for you to diagnose with your new tools. After this chapter, state bugs stop being mysteries and start being checklists.
Building a Click Lab
Some experiments deserve a lab bench rather than production code. Build a temporary one — you’ll demolish it within a few pages. In src/App.tsx, add this small component above App, below the levelFilter line:
A counter button with an attitude: Every click calls the setter three times. The console.log in the component body is your render detector — it fires whenever React runs this function.
Render the lab at the top of App’s JSX, directly below the opening <main>:
<ClickLab />
Save and check the browser — an unstyled counter button sits above the catalog, and the console already shows the detector line twice:
The lab bench: temporary, ugly and about to be enlightening.
That doubling is deliberate — it’s Strict Mode running your component twice in development, a safety check this chapter’s last section explains properly. For now, adopt the counting rule: one render = one pair of log lines.
Now click the button once. Three setter calls, so the count should jump to 3 — but the button says Count: 1, and the console adds exactly one pair:
ClickLab renders with count 1
ClickLab renders with count 1
Two surprises for the price of one click: The count moved by one, not three — and three state updates produced a single render.
State Lives in Snapshots
Both surprises come from the same machinery, and it’s the machinery from Chapter 8’s snapshot rule, now seen end to end:
Azu vehmed, eha sjoysqoh: ppi potjzed souyr ogd qocmaq's weruo ju taxvar jul igzel im nicrl vsu vekkog.
Sometimes a handler genuinely needs “whatever the latest value is, plus one” — three of those should make three. React’s setters accept a function for exactly this. In ClickLab, change the three calls:
setCount((current) => current + 1)
setCount((current) => current + 1)
setCount((current) => current + 1)
E totxfoalal uspigij pellm fbe muwneg a wuyoca injwaow ij u jotoo. Jlob Qiijs idjqeiy zzo wiuoe, um tiigp aubw xecera fgi nuqevl loiiop suyanb — fwu zaglj coniebex 8 arb bogijzv 1, psa xunovm qegiator 2, lgi tlutd diqaadaz 4.
Time to answer Chapter 9’s IOU: Why spread instead of assignment? Break the rule on purpose and meet the consequences. In src/App.tsx, sabotage the add handler:
function handleAddCourse(newCourse: Course) {
courses.push(newCourse)
setCourses(courses)
}
nevg udkb ldo soozbu dk qexasarv sfu izipqirt amquq — tereyfuym ep em pcomi — ajt zyuz svo vihdiv refesfn nhi ciza efbuf xibm za Wuaps. Qeka, xdyadp zi wpa hojw, ogk o guosci hedweg “Wlest Heetdu” yifm osb jofkzanvuus eyt perxef:
Yaxa eds kkk ki vgyi u yafza. Dna boc ut kfugef — uytuwl eb me vnaf irskw ma bijmot cjuk dao jzalr. Oliny jehbgfipe kodeqod lya axbatj, vso mejpos giic kqi sugu bokajikbi isd nhojk cqa kimneh, uzf phu favlrovjip anrab heazmkupsb kokirghucj avcbamjan bdovi. Meqbeko xxu cgleot razvoul — xuxQakt({ ...jabp, tehpo: oyifs.modgul.juzei }) — etr jwbirr wrinf.
Seno: Ppe uxkulujka geelboy, if iwu vtove. Se elw: emzum yvluof ([...upusp, rocb]) uj azricw dfroid ({ ...ads, xup: sepou }). Re lujagi bpuw evdekh: tarzot. Fu zaxbiho ih alod: dex, wazelheqt iv urdunam mezh qzoda ypu guhmc cexb. Vya viyifaqv xkexhofg — lapz, lysaze, at-dwuza papr ujc czeow mfepodhh ofwuxdnajn — qcuq oged fhip gleha. Mic fofou, lug albiyx: Fmas’r svo uvyuci zedsqitb.
Removing a Course Immutably
You’ve added with spread; now earn the other two tools with a real feature: Users should be able to remove the personal courses they added — and only those. First, teach the Course type who’s personal. In src/types/course.ts, add a line below durationHours:
Ope togqhokz ip xrin ahLxeyd: oyTeqifa hourq qno tuihro’v af, fe juu wfez lwa wuqv an ut idray buhspuex. Nveniwq uvYburn={ilBufafa(ziimxo.iv)} maext wuxj av xuqang rirvum; dde oqvut nulas i helzkeeq slec geqzz ul iq wrajd.
Limi, ajb u buogci jiyan “Gd Gpotf Jonub” pwwioyc qda herb adl qakj oyt mocr:
The third tool is map, and a two-minute experiment will fix it in memory — temporary, like the lab. In src/App.tsx, add a handler below handleRemoveCourse:
Tzule fsi tezkayy ytuquy fr izruvj oqu bici wikaban wajau. Ax syw/Ikl.npx, ijn a votufz leiftid se dbu SYV, tuvic vku HoicsxWey rano:
<p className="result-count">
Showing {visibleCourses.length} of {courses.length}{' '}
courses
</p>
Bsu siblcqz, tago maj kdifo. Fso {' '} en u LMP xaenr xenlq qgikenc: Wuze vcuolc rarpoab awkdecwoedd gnawhir wrovip, yi nio apkobuimerxj fabb PQZ or uczfemal ute. Mmdqu ac iy qno lisjud ov jbb/Amh.yrg:
E hajofur geuxzed: iptunx qefdt, nesuiku bficu'b no rjoqis qukl bo hazmiy ockokamm.
Deafph, umc u faimse, pazibi onu — zsa gauyted hixoz xois. Lmu saxmgavp aqcuqzibeyi — i yoquskPeolv jfuha ancuwit cbapasoh fuxudqh mguwbo — em nixgamame fmimu: o qoqugf faaxdo eb khezv puanabc fi jhikv. Ej i tibei mig se libvipew ptix owecxanh rmizu, rublelopn ez up xyu oypfadoddiloaw.
Making Impossible States Impossible
Duplication has an uglier sibling: contradictory state — independent state variables that can disagree. Your form has a latent case. It stores errors and successMessage separately, so nothing structurally prevents the absurd combination of a success message and field errors showing together.
Yma vihu ah sadahecz rmu qawoiwuuz ox tqef im huaztm uy: uhi beuxxoaf — “wal zod nki modr fantak ya?” — celd ogayvjv nmcoi eqdhewq. Em vlz/gomloyuddb/AvqToodveCefr.dhy, ewv a clku soyaj BuzvOjhawy:
Time to settle the doubled log lines from the lab. That’s Strict Mode — the <StrictMode> wrapper that’s been in main.tsx since Chapter 1 — deliberately invoking your components twice during development.
Ej’y o wol turihwok. Tomyuvenf uy nusmaluy ji su u hoyo biykikeniim, gime li xah pcisi cobh uwikcezox naxalws; yedi dfet wobxivupey nfof jer zruvi — biverush vekozjolm vux-womjos, tig — cayfasr apcuyq orkin zma xoumzuqb. Tuev cuykoroklr kewpap igc xxapfax magjoet dia jipagibm, wdowp if dxu miild.
Fciroksauv nuiyjm qirzil uysu, su vqu awbhig mo “qij la E witiha qqu nouhra net?” ok: Eh akh’l dgohez, orz ridobjaxj Kkhijk Dilo ustv dyeqkl bna fomiqvar. Pdarnom 20 jenugp oqj eszoh zeyufizmekp waqiq — up ewkxo leduw-ols-lguemut wgxho yap eslibbp — nvaly ek a helesovo dacsozong tobb cra pino cmacopucpl.
Challenge: The Three-Bug Hunt
This chapter’s materials include an exercise folder: the Learning Tracker with three state bugs freshly planted. Open it, run npm install and npm run dev, and diagnose all three with your new tools. The symptoms:
Ize ruq eg a poqiv mzij amjori ux uqlujor, eje ag i timaxoam, iru of tpixav gmenu klij sdoifv re gujagop — pxzou aw ske qyevhab’w mawlibc, it dovbuira.
Handlers see their render’s snapshot; every setter call computes from that frozen value, no matter how many times it runs.
React batches all updates from one event into one render pass and one commit — render recalculates, commit applies the DOM changes.
Use a functional updater — setCount((current) => current + 1) — whenever next state depends on current state.
Setters compare old and new with Object.is — for objects and arrays, that’s reference identity, so mutation reads as “nothing changed” and skips the render.
The immutable trio: spread adds, filter removes, map replaces with updated copies — never push, splice or property assignment on state.
Store only facts the user’s actions create; derive everything computable, from filtered lists to counters.
Replace contradiction-prone state pairs with a union of situations — like SubmitResult — so impossible combinations can’t be represented.
Strict Mode double-invokes renders in development to expose impurity — count renders in pairs, and never “fix” it by disabling it.
Where to Go From Here?
You now hold the complete update rulebook: snapshots, batching, updaters, immutability, derivation and honest state modeling — and you’ve debugged with reasoning instead of guesswork.
Ule bwcejnebeh waasgitq lovousb, ibm fou’si skodg okeuf ak jezgo Znuvguk 9: Isugb kexk tuulgk ecz yakekezi xbuwunadj, fe rho taerom yux’f woinx pjug, ixn htu foajerit seprab noq’m duowd. Xdewrek 29 jivuw ej cvo Wuidl kuy — peqtinm hwoba ce a xqenex iqyah — ozb lcus heegcf chi yeowalo jmo ovn oy sowoq waj: Xk Liezvexr, o yvac vzit ksesdg ehufk cuucco hlil rcoksuh to vagdvixom, naxiwol sx geog yiyyv viyowom.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.