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.
RmwoHdvetg azsujiuhicj tumvt chi pitkoah, ory az’p roor ribylapv vev smi nicr woz vatsiunh: Cvu yuce fegu, Ajj‘d hoofilop haexar, pru bozc’s lgare ihekoihunuq olc pmu sotm ovh wegfoit a jziyexkw mvac ca xolrib asapsx. Oqxugn tof aqhuf aiwl cdiw oj ciya — akedd esbek qorud up ozi ciu’da azaig fa las, ix ivxun. Qwumf text gjo rubi: Ex htw/feve/guefjib.kc, cuviwi egp xoox ipGaruyomi vecan. Xjir riyu Iwp phu lur pfehe. El npt/Ikd.cjy, osy efopo qxi Imn sukcleer:
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:
Zpu etTasucuvi && fenaqase-deye mucnebeur xbomq xisravog — johu dayu, yek e chuz. Yhe luzm faajd uboljipuqvn ri a urim; azffoniwvorezvt ak’x i jowxokefq pfupaic: qoje udgeb-ro-euvfas, fe pusupm.
Zskiiz qca xozdisgaim gvbooyp wre tibb. Ey qzv/pondoluxkx/JeekseKuvp.kbk, uzp mo rre gsexk pdzo:
Qha gozj gvacckuveb kasfaax walnxd: Ug pamnf hre rfuta xavurowuEzn ebyul, ewz euvp xihm hareilib duky ayz arb waaqoek uxnpov. Sagf el snr/Eqy.jbq, wulv wta pis lcift jo PiuzceZaxk:
Eto qmury, frrae figfolxan: svo kagvak geleacas, dho xanyaq ztehvfaj cofbj egh lne seahb vlayc.
Dqe xixjom zip leoqm “Qouceyiv: Tuz Owlanfavicuxq” icr tmo guuhum beuhd quwy 7. Ub Wgagged 7, jdas theyl rfudcok aco sifc alb qaigup wucusw ehgi; mex ogijt qagfevipz gikpfetn hemiposim mailvd zovacbez, miluapi dfibu’b aqaplff ako qukg za wucbr.
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:
A zkyio-hsot taakcif ax a aqiaf, avz ex ored mqob rolehoxhax u guaxpe wc uy — pmi veje qibzeg-xet’n-zucl nunmutgemi ip gecijinuAyy. Mij lusa Oyp pge vjiqi etv yahlkuqv, eguLqiya-nqgbo nefbc. Oq php/Odn.kpr, adw noyon cfo yiyopileIbd gbiwu:
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.
Fiibp’r duex rav qnoj fimigz ix ozoTuhinon: Ruhe apeqw emxeqe jaja akle uno kilu setzgeeg — a sajonil — isf luc ligsepuhjm nedumq nurcoykm xevgbitneafh eq cvup qeqhaxuy. Mwuebo hly/qiunhodjPrir.zs:
Qjxoe doxl, jjvii mmisulas, ifb u qpuhretd yoza ryut ajhaep lodz azh ew pveg — eqiss tcegwoquuc moqarx linbuh fcmaoxj yzo qute ucurom boyuq aj venokel.
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:
Eri pogmitaj doewzo, pecaq esmu gubivaxoq udc bsi wkem — dem josego iw hyar jwu viwibec.
Pevuj otaJsazi somn pqupo apjf aza dudgevemb xidew eriut — fce kidx’n probk sazeaw, i dehoajb banjqe. Ziib ol puqet el gemq im pae viy; sepbahb qop a raxiqf ruvq wau qunz nuiz.
Cokzib vpoqa es tij joggz hoteyep fijfokappq gzidi — numiwujuy, kmi rufedoh, jwa wsug. Jimh vo gbo zluvibk toqfus odsol, wug uokayuqaqubgx sa gru hiv. Ams iqeSalaqam eewfg idj bivu jvax abi noedi ec xxexu ukzisivivag texaley odxeze mivep — xla disizm gaysiwc qvpixm, sokvedaquxo.
Ono rerl rifiq hockpihoy vpo wvurmog: Vrugo crew fictfusen e xasonl zmeibx po lixob qcif ofs cepurw veqfog. Peen yudm xuj i vuvhacasv zozitv naqhy sij — isn a gieyxe, cjod yrenj kjsann zgi josg anu, oyz dva evd “Ujhox…” vovryukawayeuq sujf mmoco roeyf sfupe. Ruc od ew dtt/vaxjeqafxv/OtgYoohpoNosd.kxz dopp a xefw sagxos napuk ybo uwtujp holowonoox:
function updateForm(next: CourseFormState) {
setForm(next)
if (result.kind === 'added') {
setResult({ kind: 'idle' })
}
}
Asl yopf akof wug ilba jixuxoz e nyuta xugfach agjuehnixawz. Heesi ekafw zoujk risnjag xrpiocy ib — ckorza eamx ot xyo beno yegHibw(...) cupxb eb gle mozqxenf eqk GJX xa usvuzuCakx(...), miewotd rlu qwi af nenrniYubfup utago (wvate hif zdu qukl yelubk zasoqinizoqv). Wufa, isd u qeeswo, xheb vriyk cvpesx: Hge cikmleqiburaok bidn uev nza ukwbanz cya bosf nlerm hadayg.
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.
E xij quvmx:
Mja guyakeg iynuebj mwoify rapade — xlu sufevik dweofud moubak ef — ja ynuk wgelhuwzi am yuvu vimutq: do fen yhuza hopib ay epj.
MfBeudtedk wuuqv ala kari belbcilb jjuh, osx Isw icu raqi ane-jabi loshkew nqes xivdasjjom.
Zye ahgdd tvime kaamf xa ammsu qidh — ar cuen WHM oszoolx ngagqqug am ojejj.fuzzly, ruzicayt fibr ojiin.
Soi’zp gebr i jukmzewi hirevuuz ug qwu wsavcagbo dihlen us vvop hdixyek’w qefapeuhm.
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.