Data Fetching & Caching

Why fetching data inside useEffect is a bad idea, and how modern caching libraries (like React Query or SWR) solve it.

React4 min readConcept 27 of 46

The useEffect Trap

In the past, fetching data in React involved a standard boilerplate: setting up three state variables (data, isLoading, error), writing a useEffect, calling fetch(), and manually updating those state variables.

This approach is now considered an anti-pattern for production apps. It causes a 'waterfall' of loading spinners, it doesn't cache data (if you leave the page and come back, you see a spinner again), and it doesn't deduplicate requests (if two components need the same data, you fetch it twice).

Enter Caching Libraries

Modern React uses libraries like **TanStack Query (React Query)** or **SWR**.

Instead of manually managing state, you provide a 'Query Key' (like ['users']) and an async function. The library handles the rest.

If you ask for ['users'] in five different components, the library merges them into a *single* network request and shares the cached result with all five.

How it works under the hood

These libraries maintain a global cache outside of the React component tree.

When a component mounts, it subscribes to a specific cache key.

If the data is in the cache, it is returned instantly (no loading spinner). In the background, the library quietly re-fetches the data to see if it has changed (called 'stale-while-revalidate'). If it has, it seamlessly updates the UI.

Deduplication & Caching

Watch how a modern caching layer deduplicates multiple simultaneous requests, and how it instantly serves cached data on subsequent visits.

useEffect + fetch

API Server3 Requests Processed

useQuery

Query Cache ['data']
API Server1 Request Processed

Using standard fetch (left) causes 3 separate network requests and loading spinners. Using a query cache (right) deduplicates the requests into a single network call. On remount, the cache serves data instantly while updating silently in the background.

Check yourself

Pick an answer to lock it in, then read why. Getting one wrong is part of how it sticks.

  1. 1If three separate components call `useQuery({ queryKey: ['posts'], ... })` at exactly the same time, how many network requests are made?
  2. 2What does the 'Stale-While-Revalidate' caching strategy do?
  3. 3Why is a raw `fetch` inside a `useEffect` prone to race conditions?

Remember this

  • Fetching data directly inside useEffect is an anti-pattern for production.
  • Caching libraries (React Query, SWR) deduplicate requests based on a Query Key.
  • They use a global cache to eliminate unnecessary loading spinners.
  • The 'Stale-While-Revalidate' strategy shows old data instantly while fetching new data silently.
  • They automatically handle race conditions, retries, and cache invalidation.

Done with this concept?

Mark it complete to track your progress. No login needed.