Lesson 5 — Context and dependency boundaries¶
Objective¶
Expose tracker state through a focused provider and keep dependencies replaceable in tests.
Prerequisites¶
Lesson 4 — Hooks, reducer state, and derived data. You should be comfortable with useReducer and pure reducers.
Mental Model¶
Context transports a value through a subtree; it is not inherently a state manager. Every consumer reads the nearest provider, and value identity influences updates.
Concept¶
Context is a dependency-injection channel. Use it for values that many components need but few should own: themes, authentication, or a curated slice of state. The value should be split by change frequency so unrelated consumers do not re-render. Tests can replace the provider with a stub.
Example¶
const TrackerContext = createContext<{
state: TrackerState;
dispatch: Dispatch<TrackerAction>;
} | null>(null);
export function useTracker() {
const ctx = useContext(TrackerContext);
if (!ctx) throw new Error('useTracker must be inside TrackerProvider');
return ctx;
}
Real-World Usage¶
Theme providers, feature-flags, current user, and route-level state that several child components consume but only one owner writes.
Common Mistakes¶
- Putting the entire store into one Context value, causing every consumer to re-render on any change.
- Treating Context as a state manager instead of a transport channel.
- Forgetting to guard
useContextagainst missing providers.
Mini Lab¶
Task¶
Wrap routes in TrackerProvider, expose a guarded useTracker hook, and provide state, dispatch, and selected summaries. Decide whether server access belongs in this Context or a separate repository boundary.
Constraints¶
- Calling the hook outside its provider gives a useful failure.
- Tests can supply an isolated provider.
- Unrelated global concerns are not added to the tracker value.
Expected Result¶
Any component inside the provider can call useTracker. A test can render the same component inside a stub provider, and the provider value does not force re-renders for unrelated changes.
Hints¶
Memoize the provider value only where it makes identity meaningful. Split contexts by change frequency or responsibility when profiling supports it.
Knowledge Check¶
- What causes Context consumers to update?
- When is Context the wrong abstraction?
- How would you inject a fake repository?
Challenge¶
A profiler shows that all topic cards re-render when the global theme changes. If you cannot remove the theme, how would you split the Context to reduce noise?
Summary¶
Context transports carefully scoped values. Split and memoize by change frequency, and keep dependencies replaceable for tests.