Tutorial

How to Fetch with React Context for Global State

Use React Context to share fetch state across the tree so deep components read loading and data without prop drilling or a global store.

On this page

  1. Introduction
  2. Create the context
  3. Provide the value
  4. Consume with useContext
  5. Split contexts by slice
  6. Handle updates
  7. Test context
  8. Common pitfalls
  9. Conclusion

Introduction

The hardest part of share fetch state is not writing the code — it is deciding what the code is allowed to reject. A React Context setup that accepts everything is easy to ship and expensive to operate, because every malformed input becomes a production surprise. The discipline is to enumerate the shapes the system actually receives, encode them in React Context, and let the library reject the rest loudly. This keeps the happy path fast and the failure path visible.

React Context earns its place when share fetch state is the bottleneck the user feels. Before that, it is overhead that slows the common case to protect a rare one. Measure the rejection rate and the tail latency, then decide whether the guard is worth its cost. A guard that never fires is a guard you can remove; a guard that fires constantly is a signal that the producer contract is wrong, not that the guard is too strict.

The sections below walk through the concrete decisions: where to put the react context check, how to keep it fast under load, what to do when the contract changes, and how to test the failure paths so they degrade on purpose instead of by accident.

Create the context

Create the context is the first decision because it determines what the rest of the React Context pipeline can assume. A wrong choice here turns into share fetch state under load, and the invariant you thought React Context guaranteed no longer holds. Measure the impact before committing, because the cost of a bad choice is paid in incidents, not in a code review.

The test for Create the context is the one most teams skip: drive react context to the failing condition and assert the system degrades gracefully. Write it with a fake clock for time-based logic, a stub for the failure path, and an assertion on the user-visible outcome. A test that passes by timing out is a hope, not a test.

const Ctx = createContext(null);
function Provider({ children }) {
  const { data } = useQuery(['user'], fetchUser);
  return <Ctx.Provider value={data}>{children}</Ctx.Provider>;
}

The typescript above is the shape to ship for share fetch state. It is small enough to audit and specific enough that the failure behavior is obvious. Adapt the constants to your traffic and latency budget, and resist the urge to handle cases the system has not seen yet.

Provide the value

The mechanism behind Provide the value is straightforward in React Context, but the consequences ripple. A wrong choice here turns into share fetch state under load, and the invariant you thought React Context guaranteed no longer holds. Measure the impact before committing, because the cost of a bad choice is paid in incidents, not in a code review.

The test for Provide the value is the one most teams skip: drive react context to the failing condition and assert the system degrades gracefully. Write it with a fake clock for time-based logic, a stub for the failure path, and an assertion on the user-visible outcome. A test that passes by timing out is a hope, not a test.

Consume with useContext

Consume with useContext is where most React Context implementations leak performance. A wrong choice here turns into share fetch state under load, and the invariant you thought React Context guaranteed no longer holds. Measure the impact before committing, because the cost of a bad choice is paid in incidents, not in a code review.

The test for Consume with useContext is the one most teams skip: drive react context to the failing condition and assert the system degrades gracefully. Write it with a fake clock for time-based logic, a stub for the failure path, and an assertion on the user-visible outcome. A test that passes by timing out is a hope, not a test.

test('consumer reads data', () => {
  render(<Provider><Consumer/></Provider>);
  expect(screen.getByText('Ada')).toBeInTheDocument();
});

Pin the behavior with this test. A React Context setup that passes this test degrades on purpose instead of by accident, which is the difference between a managed failure and an incident.

Split contexts by slice

The hard part of Split contexts by slice is not the happy path — it is what happens when react context fails halfway. A wrong choice here turns into share fetch state under load, and the invariant you thought React Context guaranteed no longer holds. Measure the impact before committing, because the cost of a bad choice is paid in incidents, not in a code review.

The test for Split contexts by slice is the one most teams skip: drive react context to the failing condition and assert the system degrades gracefully. Write it with a fake clock for time-based logic, a stub for the failure path, and an assertion on the user-visible outcome. A test that passes by timing out is a hope, not a test.

Handle updates

Handle updates should be observable without a debugger. A wrong choice here turns into share fetch state under load, and the invariant you thought React Context guaranteed no longer holds. Measure the impact before committing, because the cost of a bad choice is paid in incidents, not in a code review.

The test for Handle updates is the one most teams skip: drive react context to the failing condition and assert the system degrades gracefully. Write it with a fake clock for time-based logic, a stub for the failure path, and an assertion on the user-visible outcome. A test that passes by timing out is a hope, not a test.

Test context

Test context is a trade, not a winner. A wrong choice here turns into share fetch state under load, and the invariant you thought React Context guaranteed no longer holds. Measure the impact before committing, because the cost of a bad choice is paid in incidents, not in a code review.

The test for Test context is the one most teams skip: drive react context to the failing condition and assert the system degrades gracefully. Write it with a fake clock for time-based logic, a stub for the failure path, and an assertion on the user-visible outcome. A test that passes by timing out is a hope, not a test.

Common pitfalls

The common pitfalls in share fetch state all reduce to one mistake: treating React Context as a formality instead of a contract. The contract is the thing the rest of the system relies on, and a weak contract propagates weakness everywhere.

  • Skipping the react context boundary so share fetch state enters unvalidated.
  • Assuming React Context is fast without measuring the tail latency under load.
  • Letting share fetch state fail silently instead of failing loudly with a metric.
  • Testing only the happy path so the react context failure surfaces in production.
  • Sharing mutable state across requests so one bad input poisons the next.

Conclusion

Do not conflate the conclusion with the implementation. They look similar from the outside but the failure modes differ: React Context fails fast and the caller knows, while an unchecked path fails slow and the caller guesses. Choose the one whose failure mode your system can absorb, and document the choice at the call site.

Self-check: write a test that drives react context to the failing condition and assert the system degrades gracefully. Then share fetch state is a property you maintain, not a feature you hope for.

Sources: React Context, useContext

FAQ

Context vs Zustand?
Context for low-frequency updates. Zustand for high-frequency — Context re-renders all consumers.
Split contexts?
Yes. Separate loading and data to avoid re-rendering on loading change.

Related Examples