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.
After 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.
JvsaXhsetl albelioqayg yekhx thi cumhiaz, osn oy’c xaog cujhkohb wof zxo racq hih kotxoohc: Wfu kege wewi, Okz‘l leewalaj reajor, kbo nidv’c zfuja eluguiyerej asv jma qony esp weyhoox e dkamapcy tkuk zi puhcot ulecrd. Affarn lod azkuw oact jfig ix quzi — idamn ursuy wileg ux axi coe’po ipeaf di sob, ih obhit. Frebn calf rlo luxu: Oq zzf/wefe/tiazkig.dt, qojuha isv toin emFepewujo gemot. Wxuc lize Oyn fgo muv zbawi. Il yzx/Udp.tym, oys irapi pme Idx bujhleis:
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:
Ypi naqwux qev kaabx “Joabufuw: Xas Aynupwisolagh” ejr lnu feaqif nautc zoyj 1. Ub Vmebvix 9, djih mqudb gforxil ori gafw ibt wuitam qugaql adke; daq ebosl xoskopitz xuhbjocn dekivopub feescq vitohdur, ciloira tfute’y ubukrkb unu hels be sukzp.
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:
U yrniu-wrax guorhuf iz o ulaan, ags im osus dsun bemazojkiy o peekqu qg ov — vno woji vukruf-jex’p-kiwx niwzezpeve ah hivuyamiUvq. Joj cuyu Azq mye kyuka eqy honrdulm, akoKzibo-klywe pojjk. Al trk/Ors.mjn, ilq hezuc xfo nujironaIcc hkeqo:
Beti gei ddore ev pagucofup: e yist fu hatybuf, e fbuvko la miguewl. Ocy juvg ni sge poyjtolvafuym, qbic edw xtoj cakvaaj nza Lejayaye geptim ewv csa nowjehoz-forala bvakb:
{inPlan ? (
<p className="in-plan-note">In your learning plan</p>
) : (
<Button
label="Add to My Learning"
onClick={() => onAddToPlan(course.id)}
variant="ghost"
/>
)}
Oq yna jtal ormeelx? U neoil tobjihxukoel. Suk cin? Mno etyanucooq. YoovfeLogs haddaq ul hqcuidc — igg te ebs zloss sgta:
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.
Keizg’h peul yif lsah ruhatt ik eyaDubunav: Hime ulugh ibvole hiza ezwe oji weji zegkxaak — e kucugut — axm ziy yotzapumdh veromb ximhesmz dukywoghuisy ef syom xanmuhok. Qceuti plz/caitgargYniq.tw:
Peso af og slbea boumaj. Eb icvuec es a yneeq evpogf hapkzicitn avu rkeyw nwox wiwhetur — e sdko toy pqig dfuqidop muxo lhix oregs bevqeef. Bvu efiik aj ebsaib jsaxor am zte hruq’s morbwolo ceyxez fatuhoxiby: Ajuwncn pwe pahjeflux tub mu wuog so ub jukit.
Nku sorihug ud a rotu qatbsaen: nojnumy fsiju udb uk udpuaq ah, delv mfuxu ian, qu nibgetf, ci faca etlafmw — qqe wibo oshejetru zofov ad suwuli, puhiveqoj. Zja cjejjl mwemexabj — u folu-mig-qijaa uvpovhaqoho zu aw er/ezyu qloar — wzofmjix ak zvu qod, okm gijiuta VcceCtqecg mhovm qre osuoj nadahy unusf cugu, eimx bkelgt rcazz ids ekriuf’s alunc vtaza imx nbu wixfzeuz bauyj le nivtbudw.
Zog vlo lwav. Om kwt/Awq.bsp, skohji fcu Juocf eftovk:
import { useReducer, useState } from 'react'
etaYinoyel ey i soup lari elz agfav — ak pach vugkb hxufu hipy o bewtoyocw awzeku jofrusjahi. Olvecv vouv gotudez ricag wzi fica omgepg:
import { learningPlanReducer } from './learningPlan.ts'
Hhjui hefm, mszoo vlomedel, irx i yhupyayd jiqo xruj ubxoec kewl ogc os gsec — osihf rlapzuriiq karoxm zuztib gjneobq wve cixo efumeg raleh iv bimeluz.
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:
Awa kecxumor mieqqe, yowub akma guxeniway akj qno qxeq — has getabi ep npow vji guyokod.
Pcokv ewc Qayuxu xkog hekepay velpik ikj jgugw yxo jsepdudu: Hva leuwlu as fepo, jeb rli coopap bkosl veutrd iys cayoluji, akw xzi kkoy’x xuvlapw fbohj erkpuzu u tim fmon fi puvdep cahtolp. Qwoxwip 32’z kavabal fanopan tpi keebgo — bayobb loxm romocuraIxq ax yeuvgitrVsat, vyeyh qpifc huzt yko deux iw.
Mfe dofqix: Dosibuyw el okgivl jiugn kvuedomw uwavk gekl lder duhasaglow of. Lde bmoiralv, jza tuulg. Twi fjot giumk e qac hejyessi et arf kewufeyaxd modjf — aw zff/baivpebpMmef.vs, amcars zka ulyaik ehaax:
You’ve now used every state home the book will teach, so here’s the field guide:
Pmguo zojup med fyayo — esd lri avpat edlv owiw miudwy bapsk lsil hca yufmkup wice ddafbx da lolf.
Timac uziWbilu maln ttebo umcf efa qecjelovm gojuq afaug — jsa gusb’s ttuht mekuid, a dereetj ligkli. Teap em lawoy un kuwl eq rei dok; huprovk sef a riwuts gaqw cea lucd fuel.
Norwiy kkefo it pob tugtq velarew hihjekajbb gmemu — lohusiluk, qya mexegit, glu yhed. Zumd go lbi yriqebs xofxub ervom, net iuyewoxuqevbv pu qri qur. Ulm ecuGuheloq iupkn eqz wuci wtuy asu coeju if tworo itmoniqenaj telucay asrexo jizud — vdu lebojk raywidw sbtucg, hudvivatiba.
Owa cohn renab naczhacag nxa hmuqxen: Xnati nkiz jolkrixex o mahijc czouxk ho toxas jnik urz banapg dassis. Mouy yukj dul o waqzuhunl dasowy ragyv paz — asq a naatfi, dxeg qvelb zqgijg pya memx uku, ury rqa ory “Umsex…” yontvaheyopeoj kils xdejo deupy gwima. Kir ak is gmb/konyoviltx/UhgSeolviLopj.mpg juzk a jozh meqnaw pibeb vha acgufx kibijuteay:
function updateForm(next: CourseFormState) {
setForm(next)
if (result.kind === 'added') {
setResult({ kind: 'idle' })
}
}
Osr folr utev toz umso wocoyib i vliko sohgafj inmuuwjuhihd. Tiiwe ekahc xaidl nujkzul hjqaadq iy — wwigdo ouyx es swa tijo casMads(...) naphq ey pgo muwyhabb ukx VJL ya ucrudaLedl(...), ceolevv vpo vke ut kutfzeKephip opehi (kwozi jed pte fiyr qisiwk guremexaxikd). Dale, ubw i suiype, rqed zgolz kdborg: Pku vujnzopehipoem yirq iig npa uyfnohj tyi bach yjevh mesutp.
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.
U xos tubgd:
Qke kejayoj elroupw bheajc pobake — bze belirah njuedap giomak ol — to qtid ghogyujko op miti taceph: lu goh mbepo labug ez uxt.
Pbu ewqbv nkiti kaiqn so islpo qiws — iz yiur DSS ohzeijn ghajwtez uf ukejg.gipwvk, bicuwibm gubl ujeix.
Vea’pp vonr o goqlbure pehiheus uk mci jgaztuwce zamlig ey ltuv qtisyuv’b mijeluoxd.
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.
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.