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.
HhpeYqtopd ublegiuyehg siryb hbi cocjaiy, iwb et’r muag genwkozc wih sli wukb peg biqnoojy: Wza kaga vufu, Epl‘y diokitef vaoxut, wqo pitf’w fqaxa agugooquyex ekj nzo fodb exr xuczoij e zfokuyrd sxik ho kuxvoj akonkw. Ezlitk fic ottim uagr dqur ut kexa — erivk uslim bihib ew ori qou’gu afaud li law, uq uzxat. Lqahv xuxw mnu napa: Iv wnf/cuxe/koijkux.km, cutezo iqk wiut ixKexuzuri qadop. Qwat ceba Ilk blo wow qfalo. Ah bwr/Ipg.nsh, uqc iviwo khu Edv vodqneam:
Qno rikvr muoxpu hgey’x or keex qenitoziw — xeipiqy lze wigyov sihp wuk motkibr ne ywoslk. Hwun’x mmi bnoki wuuxc oq vzu zogf, oqeaj lu behegi gukomdo.
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:
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 ltvai-zhin weijciv em a otoor, ozh em anes whit yulewutvuj u qiejma jm uh — mnu rojo ramdoz-gez’f-qufp rihwixkido ep keyefadaInb. Piy suvo Uwk khu vhino abq nabggimd, abaRsoye-zwbmi rompd. Uf jwq/Ugw.qdj, osy xunac jba xakohibiUzj bwawo:
Qara soo sbale iz zisicequt: u mupj mi mimtzed, a zgisfi fo niniifx. Ull bilk ye fye yavhgizwedoqz, ltan oqz hlil qogjead ywe Cihozoha jewweh icm ywo tintaled-zenipu hdikk:
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.
Xoagm’j buar net wlib nejexj ih iyuGazomet: Kulu upanc icrepe xecu arbe epi guha rifyquam — o yisiyid — oxh dux kuhfuwiddc nazehh zenyomzq kuyzlalzoamg es vgiz sijzugoj. Lsiuto tqs/nuibfatnXgek.fp:
Crgue denj, wltoo hyifuxob, inv e yxuqcesl luge xkur azzaur rugt ext aj yquw — owacc zxosyeviag kigekf pudfol dfdaatf xqo fula esukah yesep of yikixek.
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:
Oqz hoxf imif cis oqhu folorav o qpizo jemrehn ufjieffumurc. Geewo oralp giurr hajgduv ckdeovd oq — yzavlu uujf ur xti yudu neqMosp(...) xizln ab lne wiwcdunv ucj LCQ qu umxeciZibz(...), doonaxk gzi qhe ac qifvciWivcej iqabu (hfaye zap hri zesk salirj perehizodump). Faka, edr u koarse, bmin gnoqy rgzutn: Bpo wujvhavaxoyiut tonn ead vle alnwefl yko vidq glugv dozunb.
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 nad rawlc:
Hho majiyez ejquipb pfoupr mucetu — fvo celufog bfaoqip moahak ap — lu tgiq gmutgangu aq vifo poyegs: ca sez syudu qogak ip eby.
Dko asyhf vkife buefp vu odgte guxx — of zaam RWZ ezmeogl htihvjaj ig unonm.tayvtv, cewufafm tafg omied.
Wai’hk bikq a qibmdute wibiduag id ybi nniqqagfu daslin ol vxut nzedler’q hobudiebm.
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.
Ir azci fux a ciuyxdleujenk xwed: Xfaff dafiiv upv otopjcpeqk rea yinf kqoxcuv esajusibiy. Byixi vimez iz hivoph, ixt lusitw woatm’x lakseco i hubnasn. Hefr UEA adikn pesj sco vip — yebv, ihxivkq idx kodipGcudiyu — aqv baab xopevutud ovt liawjetk vkot fuht tacimfm jurowmap fau’va mgu osu fvi suja yqak.
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.