How to Intercept Fetch Requests in React
Intercepting native fetch calls in React applications is a foundational requirement for deterministic local development simulation. By capturing outbound network traffic at the browser level, engineering teams can decouple frontend rendering cycles from backend availability. This approach aligns with established API Mocking Fundamentals & Architecture principles, ensuring that UI components remain isolated during integration testing and rapid prototyping.
Exact Implementation Blueprint
To intercept without modifying existing components, implement a transparent proxy that wraps the native method. Follow these steps for immediate deployment:
- Preserve Native Reference: Capture
const originalFetch = window.fetch;before any patching occurs. - Define Route Matcher: Build a synchronous function evaluating
input(URL/Request) andinitagainst a centralized mock registry. - Patch Global Fetch: Override
window.fetchwith an async wrapper. - Intercept & Route: Match requests against your registry. If matched, return a synthetic
Responseobject immediately. - Delegate Fallbacks: Pass unmatched requests to
originalFetch(input, init). - Enforce Teardown: Always restore
window.fetch = originalFetchto prevent cross-test pollution and memory leaks.
// Core Interceptor Implementation
const originalFetch = window.fetch;
window.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
const url = new URL(input instanceof Request ? input.url : String(input), window.location.origin);
const mockRoute = getMockForRoute(url.pathname, init?.method || 'GET');
if (mockRoute) {
return new Response(JSON.stringify(mockRoute.data), {
status: mockRoute.status || 200,
headers: { 'Content-Type': 'application/json' }
});
}
return originalFetch(input, init);
};
React-Specific Integration & Lifecycle Management
In React, fetch interception must be scoped to the application lifecycle or test environment to prevent global state contamination.
- Development Activation: Wrap the interceptor in a custom hook or top-level provider. Activate it within
useEffectand return a cleanup function that restores the originalfetch. - AbortController Compliance: Extract
init.signalfrom intercepted requests and forward it tooriginalFetch. This prevents dangling promises during rapid component unmounts. - Concurrent Rendering Safety: Ensure mock responses resolve synchronously or with predictable microtask timing to prevent React 18+ hydration mismatches.
- QA/Test Isolation: Inject interceptors via
setupTests.tsor Playwright fixtures. This guarantees deterministic network states across parallel test runners and directly supports scalable Request Interception Patterns across CI/CD pipelines.
Targeted Fixes for Common Edge Cases
Implement these prevention strategies to avoid runtime failures in production-like environments:
- CORS Preflight Handling: Explicitly intercept
OPTIONSrequests. Return204 No ContentwithAccess-Control-Allow-Origin,Access-Control-Allow-Methods, andAccess-Control-Allow-Headersheaders. - Streaming Responses: If mocking streaming endpoints, construct the
Responsebody usingnew ReadableStream()withcontroller.enqueue()andcontroller.close(). - Request Body Consumption: Clone the
Requestobject (req.clone()) before calling.json()or.text()during route matching. This preventsTypeError: Body already consumederrors during fallback delegation. - Race Condition Deduplication: Implement a request queue keyed by
URL + method. Deduplicate concurrent identical calls to prevent redundant mock evaluations and state thrashing.
Validation Checklist
Before merging, verify the following:
- [ ] Original
fetchreference is fully restored on unmount/test teardown - [ ] Interceptor handles concurrent React rendering without hydration warnings
- [ ] CORS preflight (
OPTIONS) returns valid 204 responses with correct headers - [ ] Request body cloning prevents consumption errors on fallback delegation
Properly intercepting fetch in React requires strict adherence to native API contracts and disciplined lifecycle management. By implementing a transparent wrapper, teams achieve reproducible local environments without invasive code modifications. This pattern scales effectively across complex applications, enabling robust response shaping and deterministic testing workflows.