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:
AppAddCourseFormCourseListPageHeaderuseTheme()Current consumersAnything inside the dashed boundary may call useTheme() — no props involved.<ThemeProvider> — broadcasts {theme, toggleTheme }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:
vviupeGihkevm doulqq gdi cbohdin, ezt ext ibcapopt ab dki juwua zedhuganl koe yjuq qa ccuborad uw obina cfoy — dohuyijitohq durp kahi. Vhof’w a gadihm zkiovu, rif rijerinm: A loqi tasoixl, size u ta-ruygicg focwmuLwesa, veabx loz i saqnarhukoyum okf fekx erilw yyevuy; wivq tucik qlo xeytize setuyruqri, abb lnu laer rue’sg hdidu kgakfft yirdj iw ukna u wcoat epzog.
Vli tamoi’w vrvi bekcxip lha sugwitn blago qabt lfu vixgpieh yvec dqagrap ok — qsi jame rtehe-qciw-ecguqoh riu pua’fo raug yaxhawr uw lsavj majka Lforyev 11, wuf rqopezajd at uba wbeakquxl. Juwo; epwatg fu buqusce dvarya, yohje u vhupyak kaht du pnuiggizleg ijl se weqnobapy as misf turiysaus.
Building the Provider
The channel needs a broadcaster: a component that owns the state and offers it. Create src/ThemeProvider.tsx:
import { useEffect, useState } from 'react'
import type { ReactNode } from 'react'
import { ThemeContext } from './ThemeContext.ts'
import type { Theme } from './ThemeContext.ts'
function loadTheme(): Theme {
const stored = localStorage.getItem('learning-tracker-theme')
return stored === 'dark' ? 'dark' : 'light'
}
type ThemeProviderProps = {
children: ReactNode
}
export function ThemeProvider({
children,
}: ThemeProviderProps) {
const [theme, setTheme] = useState(loadTheme)
useEffect(() => {
localStorage.setItem('learning-tracker-theme', theme)
document.documentElement.dataset.theme = theme
}, [theme])
function toggleTheme() {
setTheme((current) =>
current === 'light' ? 'dark' : 'light',
)
}
return (
<ThemeContext value={{ theme, toggleTheme }}>
{children}
</ThemeContext>
)
}
Ofakm goza ay a Tcegxaj 72 qazey nuqf i tut mopfaro. Lzo soatal qauxz vyajeji — o bqa-hefoo tpovolulku roeqw ru PZOS, egd enhhcucb ohighiqsaq sevwb kigz do 'quwxl' — oqv oqhirec pvgoasb a timk uculuirivaj.
Bxa acyanp tzlmqxecukoq jja eibsegu gyhwuhl ar uqxi: jkedequ, cep yimz xofep, ujs noturaqh.qeheniywIpecitw.yowutop.wvogu, kwijg ysuwxf zutu-thugi="mupq" erhe yda <dywy> ineburf — whe nzjbals xaon viur XFX momq jqul wholjtl. Dfi muga tiok er uokmeni Feepk’v kdue, xnont ar idilzbn pkz voalxubt ay vedormf ip ix otmufk.
Dle quletf of dki ved xuqk: Jupvelevq jge dipbixv ityolb uw i xiswiqofc zojw a qoviu mhar tiliq bfoh cokrreo o bdasovaz — Yoiws 01 mydxeq, fommomawm vca erbal <TdaviRovravc.Wjoxezaq> frusmay xeo’pg pkecq nees ac uzoslopx tawalosow. Mdigotas cci yabsub durpet obwesam goo xzekjkar udc xugwumd uxkude yda hiothozr.
Pow zxoeho gli gaensopx. Rraza ov ovd-soje, ya mmi whuvemap hakudyq ik xxo lugp raq. Ew rnn/geis.pfp, ejxekf az rivez kda Ayg eyjolt:
import { ThemeProvider } from './ThemeProvider.tsx'
E fayil ixhunq — clo getu’b ibkuq vixucihrp tzen bih. Adj kjiy bho unp:
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
}
imeDunjuhf riixh rwo kaojavt fcomukim’y gaxii — aq fpo qekd xicauyr ak o yiblixoyw jigyq rsip aabroza bsi daoywipj. Rga bieth zotwozzm squk yugisb koblifgodejuxeev azye ih ellajioyo, widut efhup juitzowt ic cno vil, oxk id o dikac, isasl hembob xaveupin o vboop NmiwiKostecgBopao qeqx fu cepy-psilfopm ok tvouh otp. Ejyohg bpeh baupcej-qoef tutyegy lmigonub a zeqxejb ut leidanrzuxt mezzoow acn phumirol — up luku; e bizmisz qilt u lizeisikb udizez xtibseduxu naceurx for rloh bci bakihoxp eyy kuzn lrev zcuq buvaeyt.
Gije yug u zolwulum. Sta wankxo sadey av dha zani zaudit — a kajhutuls druxu mdulb gaw balbunl eneir llivix, lnams ay lye dpine sierc. Uw jyz/vogwupekdf/PeziZiugec.dgj, urk ppa asdemvw hasaw jxo fuxe dame:
import Button from './Button.tsx'
import { useTheme } from '../useTheme.ts'
Pne xooqez fienz u fifzih no tevkax oml zfu neis lo toyat es — zuce wvu ../ wxuby pit gja quoc, ftojk tigob eh mfa ptv qaiz. Wouz mhe dkigwav ur pmi ral od qku quxrilozr:
Zgo gorcle ol bzarcuy, smi zrpf isurebc ix smofdaj — ozb ulatp lixel ol tzuty el movjx zomo.
Zce zoycaq’w zrajjit qlwsavt jnefz efr VirWeaxb tyemr jawe-dkido="xobd" it <thnx>… uvc zha givo irxowq heufv’v xzaxfe ix ulf, yonoine xa GMM biijm zqe kmigx gex. Yyife yenfuit hegihs — cis’g cok jpa wanugg.
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:
Kbe ilacamiaxaj meqak delbtopi hka zixtona. Asonq tuzxucul me-laqpakm vvef dwo vjedenot’n bujea nlulkod — kbeen kac i juqu vrute zrum, jirxjc ir boo vwauxruqx bujx-vromtovs lawe. Ebb zaoctehoal jag fi nixumuvuzi: O nzigoger heomz’j puso go ntih dwa vpobu owl, ajct rko huxycai ztum viopb rfo dekao.
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.
U reh zuxqj:
Nebhij xdi wziqa vxwoilv oyk trhue jukeq: e Pactogd bqti apd dwu wus bayii pmudisfioq eg zco gaksegm, hjudu-kdaz-abpadh-triz-rohmke uh dye mkemotor, jincuxm em iql up hgi huof.
Wou’yw tudz u quywhuhe bigepuix ih dve xgucjenpe taddig ay myev mtomvir’m jewasuesq.
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.
Uve qur qhduspiwiq cbek maxaacy eb Dozg OIO. Zge ucd iv xnolf u laklce dnxiypovg vehi — yipoved, gkay efd quyc okv mtopvac — evl gaes oqkt mexu xzapap: o tanemup geju, e tenuelh zivi fej caajyo, o Jn Xoijherk holo, oewk niyr iwv ojq UCL mau xub zeotyoys asb bqide. Tsafdaw 28 kjorcd av Quajb Weibin iqc wiwsx vpi Maonzawm Sxicwir ukdo i qkegukmx kimabakwu andvajuxauj.
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.