Data Fetching Patterns
The evolution from manual useEffect fetching to TanStack Query - caching, refetching, optimistic updates, and why a fetching library beats hand-rolled code.
What it is, in plain English: Fetching data in React starts simple (useEffect + fetch + useState) but quickly needs more: caching so you don't re-fetch the same data, automatic refetching when data goes stale, deduplication of simultaneous requests, retry on failure, and optimistic UI updates. TanStack Query (formerly React Query) is the standard library that handles all of this, turning data fetching from a manual chore into a declarative hook.
Why manual fetching gets complicated fast
The pattern you already know
function UserProfile({ userId }) {
const [user, setUser] = React.useState(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
React.useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => { setUser(data); setLoading(false); })
.catch(err => { setError(err); setLoading(false); });
}, [userId]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <p>{user.name}</p>;
}
// This works for ONE component. But what happens when:
// - TWO components need the same user data? (duplicate fetches)
// - You navigate away and back? (re-fetches from scratch, no cache)
// - The fetch fails? (no automatic retry)
// - The data changes on the server? (no automatic refresh)What a data-fetching library adds
// TanStack Query (the most popular choice) — npm install @tanstack/react-query
import { useQuery } from "@tanstack/react-query";
function UserProfile({ userId }) {
const { data: user, isLoading, error } = useQuery({
queryKey: ["user", userId], // unique cache key
queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error!</p>;
return <p>{user.name}</p>;
}
// Same component logic, but now:
// - cached by queryKey ["user", userId] — instant on revisit
// - if 2 components request the same queryKey simultaneously, ONE request is made
// - automatically refetches when you switch back to the browser tab
// - retries failed requests automaticallySetup -- wrap your app once
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<UserProfile userId={1} />
</QueryClientProvider>
);
}
Try It Yourself (JSX)
Official Sources
- TanStack Query Documentation
- TanStack Query — Important Defaults
- SWR Documentation
- TanStack Query — Optimistic Updates
The Codex links only to official documentation. No third-party tutorials.