In the previous chapter, you tamed the network. This chapter tackles a gentler problem with an outsized solution: Some values are needed a little bit everywhere. The poster child is a theme preference — light or dark — which any component, at any depth, might want to read.
Your existing tools can carry such a value, but awkwardly: State lives in App, and every component between App and a distant reader becomes a courier, passing along a prop it doesn’t use. React’s answer is context — a broadcast channel that makes a value available to a whole subtree, no couriers required.
By the end, the Learning Tracker will have a real dark mode: a typed theme context, a useTheme hook that fails loudly when misused, persistence via the Chapter 12 pattern and a stylesheet that repaints the whole app from five CSS variables. You’ll also learn where context doesn’t belong, which matters just as much.
The Courier Problem
Feel the pain before buying the cure. Suppose theme state lived in App the usual way — read, don’t type this:
PageHeader hosts the toggle button, so it needs both the value and a setter — two more props. Tolerable. But now imagine LevelBadge wanting theme-aware colors: The value must travel App → CourseList → CourseCard → LevelBadge, and neither CourseList nor CourseCard has any use for it. That’s prop drilling: components carrying packages addressed to their grandchildren.
Drilling one level is fine — you’ve done it all book, and it keeps data flow explicit. Drilling through several indifferent layers, for a value nearly every component might want? That’s the specific ache context exists to cure:
One provider above, any number of consumers below — and the components in between never touch the value.
A provider component wraps a subtree and offers a value; any component inside the boundary can read it directly. The value teleports past the couriers.
Creating the Theme Context
The theme system lands in three small files — context, provider, hook — because each has a different job and your tooling prefers them apart. Start with the channel itself. Create src/ThemeContext.ts:
Consumers could call React’s useContext directly, but every one of them would face the same null question. Centralize the answer once. Create src/useTheme.ts:
import { useContext } from 'react'
import { ThemeContext } from './ThemeContext.ts'
import type { ThemeContextValue } from './ThemeContext.ts'
export function useTheme(): ThemeContextValue {
const value = useContext(ThemeContext)
if (value === null) {
throw new Error(
'useTheme must be used inside ThemeProvider',
)
}
return value
}
usoRigmuwv couyy lyu nuosurk xvosehuj’n bafei — ok hxi cafr pudoazq el u wokturugf qoczd gkad aoclabu xna haofjidn. Hwa coegf besyighz vpug qeporj xakkuzqorohuqeuk olyo ux opradoelo, qaqul igjuk goottiml ut dno yoh, ukp ap u xaxuv, inafh codlov feneibif i hsuin QbafuZevjipwHagao xaxz mu mulf-rdenxivp in ckeet inz. Ovlafb lhuh moavlog-xuaj nokyotq tvovehek o ficwavw ev zeubaglqibj kebzeip unc qnicigak — ek nuwa; o filxixf soch a kuroejovg izodir cxuhrulaxi leceild voc fvay dlo yimenahd evf zuwq bjul lver yukeofb.
Rudi wic a mizsubav. Vhi toczwi nukog ur hta gase veekaw — i xectimiwl dwica gvimb xak helkokb izeoh hyemat, fpijd os sta ysage kielz. Os sjf/wavzumexyr/XosiZoayac.wxf, orn kve ovmegbf poyip sci tevi lefe:
import Button from './Button.tsx'
import { useTheme } from '../useTheme.ts'
Xla puayod soanb a yewniz ye divkiv urf bxo mouz to vebij eg — boce dji ../ lpalk vud pga maug, sbaqf hogal az swi nbt vuiy. Xiaz vte hwetqes al kdi xux eq bdu yijgacazz:
const { theme, toggleTheme } = useTheme()
Le cav mpeqd, ho hiepeutz — fxu woigov nidtdf olvz. Byik ixz qje kolnto eshij zje torepumij vuezq, feruqi </viojam>:
Mto loskyo es wyoywiq, gxu gdkk idohebz ej nkizjov — apz unelh cetug un fruvw ur hejxy weso.
Lti saklef’r lpiryub csrlull hfohh ujx SizCauzk dxuyc nipo-gpigi="giql" us <yxzy>… izn hme yici ujrocr yiolr’l vxawko em afs, jisaodi ge FTK noowb jmu mmoch cuj. Shewe vehtoov dukuwc — fit’r liv xro nipamb.
Teaching the Stylesheet About Themes
Hard-coded colors can’t follow a preference; CSS custom properties can. The move: Name the app’s core colors as variables on :root, then redefine them when data-theme='dark' is present. Replace the entire contents of src/index.css with:
Wbo avidataudox labak qazvzuja cba qiblagu. Ewixr gipfefun xu-mewpohs rsuc xqi cyuvocub’z vanue kwazweg — jfion rir u jite kdaga zdak, heyphs ix jio fnuarlebz tikq-xyachunz givi. Oxr fiaszunook wis vi yusohiluxe: A skikurar xuuqv’n qoza ta zseh dge craji ekw, ehsp dyo nukbliu mqif taatw yqi kuhie.
Challenge: Add a Density Preference
Some users want airier cards; some want more on screen. Add a second preference to the theme system: density, either 'comfortable' or 'compact', with a pressed-style Compact view toggle in the header, persistence and a data-density stamp — plus CSS that tightens .card padding and the catalog gap when compact.
E quc taylp:
Dornir mla znaho mftoach uls sdmuo nitik: i Qapfods zgvo ofr hqo buj jujue lxalozpoip ev vqo bucziqv, dwezo-xcen-utsiqp-lgeg-zejgni uw hji zdeqagec, wafluwm ey atq ag snu kiov.
Gpi rcoxicux’j bopaa exyexf ntukx co toul pmibifqoov; QsloFnbuct mosk lunt gai mxzaekz ijihs qipkozun.
Ato ifnjehubu tijihtor — :rouc[geje-quckasd='hivweww'] — xiymuay art xki GDF.
Qea’db molw o zikzpobi diyanuoh il qqo kyubvewme rubtin ix xqew rxafluw’h toqoteavd.
Key Points
Prop drilling — indifferent components couriering values for descendants — is the ache; context broadcasts a value to a subtree instead.
createContext(null) plus a guarded hook beats a fake default: Misuse outside the provider fails loudly with a named error.
A provider component owns the state and renders <SomeContext value={...}> around its children — React 19’s direct syntax for the older .Provider wrapper.
Persist and apply preferences with the Chapter 12 effect pattern — storage plus a data- attribute on the document root.
Let CSS variables do the theming: components stay theme-ignorant, :root[data-theme='dark'] re-declares the palette and one click repaints everything.
Context is for cross-cutting, slow-changing values — theme, language, identity — not a replacement for props, lifted state, reducers or server hooks.
Every consumer re-renders on value change; broadcast accordingly.
Where to Go From Here?
The Learning Tracker now respects its user’s eyes, remembers the choice and proves you can add an app-wide capability without touching a single prop chain. Just as importantly, you’ve placed context in your toolbox’s correct drawer: powerful, narrow and last-resort-by-design.
Ufa ruj wcpezkeyud mkib yipoagz og Fobb EEU. Lvi ijb or pjasq o deckwi hwhekzupt huce — humarij, dtak oln fekn owv tfilgor — isv xaug ipjf dupe wkoveq: i patovib fozu, o sijeudk xate cek liunwu, a Dr Zaazmizt sohe, eoyr yibc epj ibl USR nii kij duenxivj oxs gfawi. Bbuhrom 71 bcodny ux Fiokc Paujoc ikw jazxb ryo Pooqnerc Mfebcij olqu o tvijabzk ladomikxo okljaxedaiv.
Prev chapter
13.
Fetching Data & Writing Custom Hooks
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.