Most React apps load data from REST or GraphQL APIs. This lesson surveys where API logic lives, how to structure clients, and when to reach for dedicated data-fetching libraries.
Fetching in the React Layer
Separate API client functions (fetchUser, createPost) from components. Components call hooks or dispatch actions; API modules handle URLs, headers, and serialization.
Avoid scattering raw fetch URLs across components — centralize in src/api/ or feature modules.
// api/users.js
export async function fetchUser(id, signal) {
const res = await fetch(`/api/users/${id}`, { signal });
if (!res.ok) throw new Error('Failed to fetch user');
return res.json();
}
// Component — via TanStack Query (recommended)
const { data } = useQuery({ queryKey: ['user', id], queryFn: () => fetchUser(id) });
API functions are plain async — testable without React.
API Layer Structure
src/
api/
client.js // base fetch wrapper, auth headers
users.js // user endpoints
posts.js // post endpoints
hooks/
useUser.js // optional thin hook wrapper
One module per API domain (users, products).
Shared client handles base URL, auth, error parsing.
Use env vars for API base: import.meta.env.VITE_API_URL.
TanStack Query owns caching; API functions stay pure fetchers.
API Integration Options
Tools for HTTP in React apps.
Tool
Best For
fetch (native)
Simple requests, no deps
axios
Interceptors, cancel tokens, ergonomics
TanStack Query
Cache, refetch, loading states
RTK Query
Redux ecosystem API cache
GraphQL (Apollo/urql)
GraphQL APIs
MSW
Mock APIs in dev/test
API Error Handling Strategy
Normalize errors in the API client — return typed error objects with status codes and messages components can branch on.
Auth Headers in API Client
Attach tokens in a shared client wrapper. On 401, trigger logout or refresh flow globally rather than per-component.