Working with asynchronous operations
1. React itself is a synchronous library
React does not know when or how you load data, call an API, or run promises.
Its job is to render the interface based on state (state and props).
Asynchrony appears when you change state as a result of an external operation - and then React re-renders.
2. The typical way - through useEffect()
Asynchronous operations in function components are usually run inside useEffect, because:
- the effect runs after the render,
- you cannot
awaitdirectly in the component's body (a render must be a pure function).
Example:
import { useState, useEffect } from "react";
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let isMounted = true;
async function fetchUser() {
const res = await fetch(`/api/users/${userId}`);
const data = await res.json();
if (isMounted) setUser(data);
}
fetchUser();
// cleanup - if the component unmounts before the request finishes
return () => {
isMounted = false;
};
}, [userId]);
if (!user) return <p>Loading...</p>;
return <p>Hello, {user.name}</p>;
}Important: useEffect cannot be made async directly - instead, you create a nested async function.
3. Asynchronous operations = side effects
Classic examples:
- loading data from an API (
fetch,axios, GraphQL); - interacting with WebSocket or SSE;
- timers (
setTimeout,setInterval); - operations with local storage (
localStorage,IndexedDB); - working with external SDKs (for example, Firebase).
All of them run outside React, but affect state.
4. Modern approaches
React Query (TanStack Query)
One of the most popular ways to work with asynchronous data is TanStack Query. It:
- caches results;
- automatically refetches on window focus;
- supports pagination, invalidation, optimistic updates.
Example:
import { useQuery } from "@tanstack/react-query";
function UserProfile({ userId }) {
const { data, isLoading } = useQuery({
queryKey: ["user", userId],
queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
});
if (isLoading) return <p>Loading...</p>;
return <p>Hello, {data.name}</p>;
}5. Asynchrony in React 18+ (Concurrent features)
React 18 introduced new capabilities for asynchronous rendering:
startTransition()lets you mark updates as "non-urgent" so they do not block the UI.useTransition()anduseDeferredValue()let you defer a render or update the UI smoothly.Suspense+React.lazy()are for lazily loading components and waiting for data.
Example with Suspense:
const UserProfile = React.lazy(() => import("./UserProfile"));
function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<UserProfile />
</Suspense>
);
}In React Server Components (RSC), asynchrony is built in - server components can be
asyncand useawaitright in their body.
6. The key idea
Asynchrony in React is not the same as asynchrony of React itself. React stays deterministic and synchronous at the render level, and asynchrony is a way of changing state from the outside.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.