Navigate
Search topics, hooks, and fundamentals
React effects and async code need careful coordination. Learn why useEffect can't be async and how to prevent race conditions.
useEffect expects its callback to return either nothing or a cleanup function. An async function returns a Promise — which React can't use for cleanup:
// useEffect callback can't be async
useEffect(async () => {
const data = await fetchData();
setData(data);
}, []);
// This returns a Promise to useEffect,
// but useEffect expects a cleanup function (or nothing)// Define an async function INSIDE the effect
useEffect(() => {
async function loadData() {
const data = await fetchData();
setData(data);
}
loadData();
}, []);Try loading data inside an effect — see how the async function is defined inside and immediately called:
When the component unmounts or dependencies change, you need to cancel in-flight requests. Use AbortController:
useEffect(() => {
const controller = new AbortController();
async function loadData() {
try {
const res = await fetch("/api/data", {
signal: controller.signal,
});
const data = await res.json();
setData(data);
} catch (err) {
if (err.name !== "AbortError") throw err;
// Fetch was cancelled — component unmounted
}
}
loadData();
return () => controller.abort(); // cleanup!
}, []);When the dependency changes faster than the fetch resolves, older responses can overwrite newer ones. Use a cancelled flag or AbortController to prevent this:
// Race condition: fast type, slow network
// User types: "a" -> "ab" -> "abc"
// Fetches return in wrong order!
useEffect(() => {
let cancelled = false;
async function search() {
const results = await fetchResults(query);
if (!cancelled) {
setResults(results); // only update if still relevant
}
}
search();
return () => { cancelled = true; }; // cancel stale request
}, [query]);The slowest fetch ("a" at 1500ms) arrives last and overwrites the correct result.
Stale responses are ignored — only the latest query's result is displayed.
useEffect can't be async — define an async function inside and call itTanStack Query for production data fetching — it handles all of this for you