Skip to content

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 useContext against 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.

What Comes Next

Lesson 6 — React Router and lazy routes