What are common async data loading patterns in React?
ReactThe short answer
The most common patterns are: fetch in useEffect with loading/error/data states, custom hooks for reusable data fetching, data fetching libraries like React Query or SWR, and fetching in route loaders (frameworks like Next.js or React Router). Each has tradeoffs between simplicity and features.
Pattern 1: useEffect + useState
The most basic pattern. Manage loading, error, and data states manually:
function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const controller = new AbortController(); setLoading(true); fetch(`/api/users/${userId}`, { signal: controller.signal, }) .then((res) => res.json()) .then((data) => { setUser(data); setLoading(false); }) .catch((err) => { if (err.name !== 'AbortError') { setError(err.message); setLoading(false); } }); return () => controller.abort(); }, [userId]); if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error}</p>; return <h1>{user.name}</h1>;}Pros: Simple, no dependencies, full control. Cons: Lots of boilerplate, no caching, no deduplication, easy to forget cleanup.
Pattern 2: Custom hook
Extract the fetching logic into a reusable hook:
function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const controller = new AbortController(); setLoading(true); fetch(url, { signal: controller.signal }) .then((res) => res.json()) .then((data) => { setData(data); setLoading(false); }) .catch((err) => { if (err.name !== 'AbortError') { setError(err.message); setLoading(false); } }); return () => controller.abort(); }, [url]); return { data, loading, error };}// Usagefunction UserProfile({ userId }) { const { data: user, loading, error, } = useFetch(`/api/users/${userId}`); if (loading) return <p>Loading...</p>; if (error) return <p>Error: {error}</p>; return <h1>{user.name}</h1>;}Pros: Reusable, less boilerplate in components. Cons: Still no caching, no background refetching, no deduplication.
Pattern 3: Data fetching library (React Query / SWR)
Libraries like React Query handle caching, refetching, and deduplication for you:
import { useQuery } from '@tanstack/react-query';function UserProfile({ userId }) { const { data: user, isLoading, error, } = useQuery({ queryKey: ['user', userId], queryFn: () => fetch(`/api/users/${userId}`).then((r) => r.json()), }); if (isLoading) return <p>Loading...</p>; if (error) return <p>Error: {error.message}</p>; return <h1>{user.name}</h1>;}Pros: Automatic caching, background refetching, deduplication, retry on failure, stale-while-revalidate. Cons: Extra dependency, learning curve.
Pattern 4: Framework-level data loading
Next.js, Remix, and React Router support loading data at the route level:
// Next.js App Routerasync function UserPage({ params }) { const user = await fetch(`/api/users/${params.id}`).then( (r) => r.json() ); return <h1>{user.name}</h1>;}Pros: Data is ready before the component renders, no loading spinners, better for SEO. Cons: Tied to a specific framework.
Which one to use
- Small apps / learning — useEffect + useState
- Medium apps — Custom hooks or React Query
- Large apps / production — React Query or SWR
- Framework apps — Use the framework's built-in data loading
Interview Tip
Show that you know multiple patterns and their tradeoffs. Start with the basic useEffect approach, then explain why libraries like React Query exist (caching, deduplication, background refetching). If you can mention that modern frameworks like Next.js handle data loading at the route level, that shows you are up to date with the React ecosystem.
Why interviewers ask this
Data fetching is something you do in almost every React application. Interviewers want to see if you know the common patterns, understand the tradeoffs, and can pick the right approach for the situation. A candidate who only knows the useEffect pattern is fine for junior roles, but knowing about React Query and framework-level loading shows senior-level thinking.