Skip to content

API Calls in React

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.

async function api(path, options = {}) {
  const token = getToken();
  const res = await fetch(BASE + path, {
    ...options,
    headers: { ...options.headers, Authorization: token ? `Bearer ${token}` : '' },
  });
  if (res.status === 401) logout();
  if (!res.ok) throw await parseError(res);
  return res.json();
}

Common Mistakes

  • Fetching inside render instead of effects or query hooks.
  • Duplicating fetch logic across components.
  • Hardcoding API URLs instead of environment variables.
  • Ignoring error and loading states in UI.

Key Takeaways

  • Centralize API functions separate from UI components.
  • Use env vars for base URLs and configuration.
  • TanStack Query handles cache and async UI states.
  • Normalize errors in a shared client layer.

Pro Tip

Define TypeScript types or Zod schemas for API responses at the client boundary — catch shape mismatches early.