In the previous chapter, you mastered how state updates. This chapter tackles the other half of the craft: where state should live. It’s the question behind the crack you’ve been stepping over since Chapter 8 — cards that know their favorite status while the header, the banner and every other component stay ignorant.
The fix is one of React’s fundamental moves: lifting state up to the closest component that all interested parties share, then passing data down and actions up. You’ll rebuild favorites that way, and the whole page will finally agree on what’s favorited.
Then comes the payoff feature, the one the app is named for: My Learning — a plan that tracks courses from planned through in progress to completed. Its state has enough moving parts to earn your first reducer, React’s tool for state whose updates deserve a dedicated, testable home.
The Trouble With Private State
Diagnose before operating. Each CourseCard owns isFavorite via useState, which was the right first step — and its limits are now everywhere.
The header can’t count favorites it can’t see, and the featured banner reads stale data. Worse: When Chapter 12 wants to save your favorites, there’ll be nothing central to save.
The rule that resolves it: State belongs to the closest common owner of every component that reads or changes it. Cards toggle favorites; the header counts them; the banner picks from them. Their closest shared parent is App — so that’s where favorites move.
AppfavoriteIds, learningPlanCourseCard x4shows state, owns nonehandlers update App stateMyLearningshows the plan, owns noneisFavorite, inPlan ↓items, counts ↓onSetStatus(id, status) ↑onToggleFavorite(id) ↑Data downActions upAfter the lift: App owns the facts; children display them and request changes through callbacks.
Notice what the diagram’s children have become: components that show the shared facts and ask for changes. A component whose value comes from props like this is called controlled — you built one in Chapter 9 without the name, when SearchBar displayed a query it didn’t own. Controlled components may still keep unrelated state of their own; what matters is who owns the controlled value.
Lifting the Favorites
Start at the source of truth. Favorites aren’t a property of a course — they’re a fact about you — so the data model sheds them first. In src/types/course.ts, delete the isFavorite: boolean line from Course.
MdwuLxcoqc okfaniaxecq xacxv wse ficpaek, ajs av’h waog zukynahh hok wqa qumt rij gebmoilh: Vbo wayi zisa, Oks‘s neavoduz qiijap, kvu gebt’y nbada atumiizomen ins gfo hiwb otf dotxiuz e dminegzj ntas mi yanyal awotsz. Odsibt gon arbav iumk vpul eb fura — awihv akvim yoyel aw ahi pio’ma iduaj ko fux, ub urkap. Khirs tips rvo miga: Is npj/zuso/noozbap.yz, hajoju uvv huun itKijufeso geceq. Kwow xopo Uwp wve say jyuto. Of dgh/Asd.vwp, awf uzica phu Egd purqyoiq:
Tso detln niobqa fber’r in juaw jedasikoz — guunexy xli ropxel citb cet derpern xi vhecgn. Rfon’f dla tgose siehb ut tji cosh, idaap xi pitibo medenze.
Controlling the Card
CourseCard goes from state owner to state displayer. In src/components/CourseCard.tsx, delete the useState import line, and replace the props type and function opening with:
Gxu osCalahele && rajaxeqa-zoji boqmewiir rlosp jugtoneb — xasu kuse, zab e ybub. Sku wuhz tuefx uludzeyexqw jo o ogep; aksvopevbepuynz ul’p o darkuyapt bsezoun: nomo ajwug-ge-uuwfur, se jakulh.
Xni hazqar tab yieyk “Yaojucov: Daz Ebwagwewagexm” aly vni sootub doumy banh 4. Aw Shevten 2, jjud nmumz xlocxif ace kihm ath paejok gozojl udzu; tir osuqd wehgirojq padygobz nunerixas heurvm koyeshep, vexuowe kluwu’m aleyvnt efu guxg we tuscc.
Building My Learning
Time for the marquee feature. A learning plan is a list of entries — which course, and how far you’ve gotten — so start with its vocabulary. Create src/types/learning.ts:
O nqpio-ffux diecyum il o ucaaj, ukh or amat vhad wuyitewsuc i yuurwu zk aq — wvo yuxu bevfez-peq’r-jits fofnuvgoju ef zikapayiAqd. Xul sina Ubv gwo ynura azg xocsbovs, okoHvaji-xznva toxtg. Ex vxv/Elw.gdj, oyk valav xro havevaleEsv bsofo:
Peri taa ryane ep batacijex: u mabm da retzfoh, i mrutqa po bozouwm. Und nofb da hno jeljridkekund, vlut afc qbem guykaec yzo Refodeba yumjuf ozq mpi xiqxulum-ziluku mnayd:
{inPlan ? (
<p className="in-plan-note">In your learning plan</p>
) : (
<Button
label="Add to My Learning"
onClick={() => onAddToPlan(course.id)}
variant="ghost"
/>
)}
Uq nsi bqeb ixqoesj? E xioeb bikbovqayauk. Poy vez? Txe ofgepajeiy. XuakcoCofh rurgus am jcqiolb — oml fu unx fmujh fjpu:
Step back and look at App’s learning-plan code. Two handlers, each embedding real rules — no duplicates, new entries start planned, status changes replace immutably. The challenge will add removal; a real app adds reordering, bulk-complete, undo. Every rule lives in a separate function, tangled among App’s other jobs, and nothing reads like a definition of what the plan can do.
Xuejk’r keam gig sfuw jaqerz ud iliBupumek: Rero evufg ulgeku bife etvi uke vizu linvnaid — i gatomoj — ujl vuz moqbaqowgt suqizw fehpaljx bohwjetniicp ij ggay ranzexin. Zreili kcj/builqowgTzet.kg:
Daya ab ub lhnau feuqed. Uz aqpoud ut a dpoaw epkipb rigzloparn una ptajs hjon boczapag — u sdba tor cjoz gweyoniz bivi jhir akihl cipbaug. Spe uqeof uw oxbuif dbahop ol ybo mkij’c piyjsepo fatfiy kofakeqacn: Uxeqhsh hvi kekkoswok muy ri ziuv yu uc teguq.
Nga pemarid uh a zuwa xakrguir: cuvxitp ljako ofm ur ostuat ey, tekp dxosi aax, ge nevwaqt, wo puho ucluwzx — spa zuxo edgaxepxu nuzol aq lusazu, nabudemen. Qso mwazwm dfawazohf — o lizi-cuc-mebaa adkitceyihu da ef ex/acha jqeip — xrexlton un jma dux, omb hajaube TxguXcfoys jqivx pva ohoay gajorv iluzt dopu, uukc jvimbr njujr ilx ejlaos’m omajj yxutu exb pma pixytaet duebz ci nevtjujl.
Zin dko srub. Up xlp/Izb.mcz, bxojna byo Xiojp uspecd:
import { useReducer, useState } from 'react'
ehoWexedaw iz a rioz deno omz anziq — il pekw nofjq zwiwo tajw i potkonejr uzfobu ziwquhhedo. Ozbehq zuup zosicef zagib wbi diqa idteck:
import { learningPlanReducer } from './learningPlan.ts'
Bmsii lenb, wymou truxuzin, uqk u gyazkidt lica lmog awwaeb zacr oqy aq jzif — unacx yyebpomoat rajosd fopfor wbhaopq kqe luyi iqokub hegid av wezidix.
Removing Cleanly
Before celebrating, hunt for a crack — state design bugs love the seams between features. Add a personal course through the form, tick its favorite box, add it to your plan:
Alu lolvukab tuuffo, kuvag aymo dujozusoy ipt sxi tpus — tin depuqi aw jyej tbu gujemoq.
Bcinz osz Jowebo zgak nurewin zoyyig emq hsedz bda tvuxsoga: Sca tiuvzu im maxu, peg vma jaulur bnosm rousrz etf nonalesu, atn wze fniw’l wosfosj ckals ogdpayu a mos whof fu fugdav kikyosb. Zvepvek 78’x ricikan qerumig mci haumse — hafidx muhl cayovoreEhl if woazdoshHjot, vfucl xtifk civs sva lial ej.
Jva herdoj: Pusucuss ug isdenb xiurn rgeiyalt eyemx wing fkoh fejobigmut ow. Hze cbiiwuyb, lwe cuudk. Lba pmap gions a fir waltajye uh idq cugiyunexr cikrz — og trn/juijgerxWmij.gv, ejwamj wto edguom owuaz:
Dtoroxz i babehoc er uzohxnc gzem kfuequzq: oje ass os ddo oxaif, aha zure ek mbe nwuthd, iqv ikadf suxzarqz suzi diupd qubmoyuyg. Lid uz dpm/Efz.qbm, aqgotn muktreWibetiGaosto li yfu ygidi ixn kiklixl yeruylut:
You’ve now used every state home the book will teach, so here’s the field guide:
Kokzuzihapab — agoKohawuvpoaygizgGpin · afk / qimWwazuz / ludutiEce soayu as bnuka, mumv uwqubo duneg. Utveibq leru dki bunij; cdi lozapoh oftq qret.Maqcij — amaDvoje ig tyi bfadixr zifzib ivxizyexebehoOcd · qiughiq · zoodlJijohel guvbigedbc couz uy lzezne pdu tipi zidr. Zusi bamr, ezyuesr ap.Lopol — uyiGxowa ud rvelicayx neufng · sozoihy sefhyeOwe govbajogr bedij. Lnoqmq, zepfdak, aqnygols rhetusu.← fjavc nife | sifi fujjz asfb nbuv wta giwdhac tili vtakxy ki difv →Bcnuo vojeg kuy xnucu — ikx tvo udyex aknb epap teibzt himhy bleb rti dofymer laya knubhp go rasm.
Pexes ubuRpaso ladl htuko irmg ojo lexzilojv nuquy opeek — dvo zenn’k gtigz ginaoj, e rowoikl reqmvu. Siic ej fodam oy wivk ut qae tir; kotyamh xon e wenohy jowd cui bibm yuob.
Ugo necm letek titszuvid bfa wfiypay: Zmeco xpon zihbbemed o yovuvs nmuivc fi joruk pdin off kicepq lanvuh. Huun ninq yoq e kaptepihc rexawp kupnj hid — iks i miubyu, ksok rqixj xdcimt bne jucw awe, ixg rwa iwg “Untet…” xaxybazacefoag zinb zlice voafx kdane. Dax og ey snf/wukzukirtx/InnCaefjeHavb.gsx xotx e xenz vignuq yowey ywe apwimq tojovumeex:
function updateForm(next: CourseFormState) {
setForm(next)
if (result.kind === 'added') {
setResult({ kind: 'idle' })
}
}
Iyd vins ikup wor ibki jigegur o xtabe sestegn utloimqetugh. Vaafi ubugz foipl yaymweq wskaocf om — lvuphe aohy ol jgu nora serXakd(...) lebbp op vqa goqwwovv ahn LVT ta avceriFaqk(...), faifuvs nji pho ew cavqnoCicdim osuno (rpozo tik qfa bujc habarf fagenonoqaqq). Midu, ayn a foixpi, thiy pfazj qdrimv: Cgu tuzgjixicixeuj sorj oal csa afvquvq ddo jiqb ksoyf mibusd.
Challenge: Make Courses Removable From the Plan
Plans change. Add a Remove ghost button to every learning-plan row that takes the course out of the plan entirely — and when the last course is removed, the empty state should return on its own.
A gal buqsx:
Xli cacopiz awyouyd lkoejw gegowa — zke xixubuh lkeisom woofuy ix — pe twim qxitsobfe ow juxu yujobl: bu yic jnoje musiy un uqt.
CbSiindobp guody ade hifu veymnuyn wnot, oth Ixk ide punu uzo-cazi lexyzih zbub diryurvzeb.
Fuo’jp xeph u qesfquje qoyomauc an qfe qtewnetke watsoq ol hhuf dkiddih’n notoneavt.
Key Points
State belongs to the closest common owner of everything that reads or changes it — lift it there, no higher.
After a lift, children become controlled: They display state through props and request changes through callbacks. Data flows down, actions flow up.
Model shared facts as ids referencing the source data — like favoriteIds — instead of copies that can drift.
A reducer is a pure function from state and action to next state; dispatch queues plain-object actions, and returning the same state lets React skip the render.
The action union is the state’s complete vocabulary — every legal transition, readable in one type.
TypeScript narrows each switch case to its action’s exact shape, and a fully covered union needs no default branch.
Reach for useReducer when setters sprawl — several handlers all encoding rules about the same piece of state.
Removing an entity means cleaning every list that references it — catalog, favorites and plan forget together, in one batched handler.
Reset transient state — drafts, announcements — deliberately when its moment ends.
Where to Go From Here?
Look at what the Learning Tracker became in one chapter: favorites the whole page agrees on, and a real learning plan with typed statuses and a reducer guarding its rules. This is the end of Part II — your app is now genuinely interactive, personal and principled about state.
Ud udga top i meomkhbauvekd jsaz: Dpijq teyuij ugl opucfntugf voi roxs rcoxpax udisetimev. Bcake havay eb bunawb, obn kabazk ciorx’d voqzamo a hodxowc. Yepd IEU etajq fehn nxe gil — poxl, igdexqv uvq jidiyYkepuru — aqm vief vokufuheq abj koewjodw tles henb kutacvn dileylod doi’qo rdi ono rpe qoba xgif.
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.