IntegrationsREACT
Handling React fetch CORS in browser applications
React components fetching data from browser code encounter network errors when remote APIs omit CORS headers. Structuring state transitions correctly exposes whether a failure stems from network errors, HTTP error codes, or browser origin rejections.
Track loading, error, and cancellation states
Data, loading, and error states must remain separate. Fetch operations initiated inside a useEffect hook need an AbortController instance so unmounted components cancel pending network requests. The browser resolves fetch() promises on HTTP 4xx and 5xx responses, so checking response.ok before parsing JSON is required.
import { useEffect, useState } from 'react';
export function WeatherNow() {
const [temperature, setTemperature] = useState(null);
const [error, setError] = useState('');
const [loading, setLoading] = useState(true);
const [updatedAt, setUpdatedAt] = useState(null);
useEffect(() => {
const controller = new AbortController();
async function load() {
try {
const response = await fetch(
'https://api.open-meteo.com/v1/forecast?latitude=51.92&longitude=4.48¤t=temperature_2m',
{ signal: controller.signal },
);
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const data = await response.json();
setTemperature(data.current.temperature_2m);
setUpdatedAt(new Date());
} catch (cause) {
if (!controller.signal.aborted) setError(cause.message);
} finally {
if (!controller.signal.aborted) setLoading(false);
}
}
void load();
return () => controller.abort();
}, []);
if (loading) return <p>Loading the forecast…</p>;
if (error) return <p role="alert">Could not load the forecast: {error}</p>;
return (
<p>
It is {temperature}°C in Rotterdam.{' '}
<small>Updated {updatedAt.toLocaleTimeString()}</small>
</p>
);
}Rendering must branch across loading, error, and resolved data states. This structure prevents incomplete renders and handles the abort rejection when React unmounts the component during dependency updates.
Branch when the browser blocks the response
The browser enforces CORS policies regardless of whether React, Vue, or vanilla JavaScript initiates the request. If you control the upstream server, add Access-Control-Allow-Origin to its response headers. When a third-party API already includes these headers, requests succeed directly without proxying. For public APIs that omit CORS headers, route the request through a proxy.
// Before
const url = 'https://jsonplaceholder.typicode.com/todos/1';
// After: same request, re-served with the headers your origin needs
const url = 'https://proxy.cors.dev/https://jsonplaceholder.typicode.com/todos/1';Prefixing the target URL with https://proxy.cors.dev/ routes the request through the public proxy, which adds permissive headers. The proxy operator can observe traffic routed through it, so use this route for public data only. Supported upstreams include open data providers such as Open-Meteo, GitHub, and Frankfurter.
Poll on intervals and communicate staleness
Polling keeps public data fresh when components remain mounted. Align interval timers with the upstream cadence, clear the timer in the effect cleanup, and display a timestamp so users judge staleness. The anonymous tier on cors.dev applies fair-use rate limits per IP address, which accommodate typical polling cycles.
Good questions.
Should I use TanStack Query or SWR instead?
Libraries such as TanStack Query and SWR manage caching, retries, deduping, and lifecycle states for complex applications. They do not alter browser security checks. When an API omits CORS headers, these client libraries fail with the identical error until the server sends the headers or you route through a proxy.
Why does my effect fetch twice in development?
React Strict Mode mounts, unmounts, and remounts components in development to highlight missing cleanup logic. If an effect starts a fetch without an AbortController, both requests run to completion and race to update state. Passing an AbortSignal to fetch() aborts the first request during unmount, leaving only the active request to commit state.
Where do I put the API key?
Upstream third-party private API keys belong on your backend, never inside client-side React code. Anyone inspecting network requests or bundle assets can extract exposed secrets. For cors.dev project keys on the free account tier, send the key in the X-Cors-Key header. The key is a publishable identifier rather than a secret: origin checks stop other websites from reusing it in browser code, and you can rotate or revoke it from the dashboard at any time.