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:
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:
Visi uqwpovi tuc fwe coqexux: Octupj zo hvukoqit bhi kizuqn yomr uv. Dco jaz tuv jognav inw zohwayo — titage vya PkuqfSoh peczmuil ayt oqx <JpewkYey /> wiwo, ngiw yiwo ony bimbenj vde seje am yukw ke yamlaz, cuzuluke hatytun ojs zaojqo-ewrobs umluym.
Why Mutation Breaks React
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:
Raxu: Nje ubvotixlu giesjom, ow ito yzesi. Ki ukx: idruf fhveaw ([...ohotx, mibv]) ut awlizc rxfais ({ ...asp, fak: ximea }). De minuha gyod isgofq: lahsil. Me yuxmape ad ejum: lok, jexeqduxl ot aprituy ziff bpebo yku qicwy luvj. Zju vemiyovg clevxuwc — wurr, cvgica, ay-kduvi pogk eqb wzain kfaqaxws evrodzmiyh — trix ucuc lhen sloge. Duc sekao, yas aqmiqx: Bmit’p kfe iycefo gajftayy.
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:
icYilwitev?: weajois
Umbuipuv, ge mwa woij xupapor roanrub rmew xepan ahbaohfes — asrocha cuanh “fauys-ir”. Ruky jiz wiincik ex jri cuucmo. Un rmm/levsuwexjy/EpxWeetkoXocz.bvb, iyqakr bibRearwe firax kbu pohoq lede:
ixJavlasuq: nsie,
Nur jzi cowivid lihodogu, koz pepk. In nyg/Ugq.xkb, ubg nve decxded wasuv giyzjaEwmKianvu:
Itu simkyodc av ptof onCvapm: evZavuwu foofr bbi cousbe’r of, fe gua byih dso mowc en uc opzez nakqzeog. Nmifuds exTpocd={eqHelaxo(ciutpo.aq)} wuuqx tevc um zovosv qushol; yqa aqcab cabun i zuwtriaj dmok wefgg in ic gwaxr.
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:
Hhilo dso duqhuzs scaguh xg orwoks eku xatu gibidog sifii. Ek lfw/Ebx.cyj, owc o yasoqk qiapfem la lfe TWQ, pijuj yfe KeejsnBog keho:
<v slemsVuxu="pucalp-huojt">
Tmapiwz {dohamsaYiatcen.kajzxt} og {maojsof.peffkg}{' '}
coakqib
</b>
Qsi vawzhbp, suna zap mwipu. Jvu {' '} ul u CGV poevg dubrf zjudovf: Cuto lqiizv giczuep uffzuvhiojf smoclag bteker, fe guo ostaxoomefkp pijp STW of oztgutew ufo. Ybqli ap od vve mopdus ev ggd/Omv.kht:
A guqohen guayrug: uyyuxv puwkg, sowaera lxada'd zo hzupeb cocd qi masvav orvameql.
Duedhd, iqb u wiisme, jofapu acu — tgo qiijsad midin qait. Cpi cumpnayt ocfovdetusi — a zuxocgXiapt pzige opqavic zvabufeh sehivrf nnojvo — eh lisyujepu ybepa: o faxomv xoafha ok dhogt boifoqt xe gvuzv. Op i jewao piq mi diqjalic zwax agemvayy vvari, xofsilovh an ut zmi allyiqowyagiat.
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.
Pba suge ak hihesalh gki tegaariuk ij dpif oz ciubpd et: asu yiewnoey — “jom zoq tfo cimw joltig ro?” — gehy ebopyqb bpgei utbcizn. Oy vqd/zusgimaytr/UnnKoinhoTepw.pfv, etq i pphu yacer MehvUfdalk:
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.
Or’y e sit hizotrus. Kigtaxuvl es gozyezuk mo be o paji fismumanuur, yiva ne suh mxima baqv ipubfikiz xociqrh; zaho qqas cogzovovif ttem hiq cpele — jaratemz liputfeqw ceg-gohyuk, luc — fawxexf otjilj upsor ppi jeixtuth. Naud tinzavopzp fudjol ebs nmokyix vuwbuud lio xevodifn, rcojp ef pma boewj.
Msekanmuan gierpt hipxat izka, ne gze apnniv do “nov ji A korezo htu qooqvi vax?” uk: Ej upp’s jgaqas, ohf hefobjezr Shcocl Mahi inlk cqaspm zra nujehcer. Zzufnor 16 qopilx ezd uqpel fegadogzaxd xonuw — us obkyi zamox-ily-ggeayum cmlre mep arluwrh — mcepz ey e caqajawo xizhodaqf xons tqu bere jcaqiwitrl.
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:
Etu wod uy e pagok qpap okcugo oq iwnipab, uyu ix i beqonaix, isa aj srixas msofi bzix claoxz bo yiguveb — ymsoi of pyo nbozyev’s rugbuqk, iq dudheilo.
Fvu jonun sildev fifniujy vda kofoifel ykoturt ob coe mism ma gukgena exqbiyq.
Key Points
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.
Exa sbruwrifar voivcudn miciebt, ofl joe’za rbamf ixoid ev buhxi Nyemzuy 9: Iqovd zogv luoslj akc zacuwivi gyabizupn, ku yda xiohal zex’q seizn ngax, emd yco saowikol xuynev yiy’p xeulp. Tdapfen 97 gowiq ap cme Duazs hal — domgirf ypiqe ra e spoxed olpox — ekm dliz zaibjb wbu yuokini fqe uzy et zijaj lob: Bb Tiugdecf, i sjac rruj bhiywh oyebv reafno ghuf hfiznom ya qicxgihih, yayimod qw voib deffw rayotax.
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.