Learn React Query by building small practical examples
Compare React Query and SWR
Understand how modern data-fetching libraries work under the hood
Build intuition for which tool to choose in real projects
React Query is another powerful data-fetching and state management library for React applications. It goes beyond basic fetching and caching by offering extensive tools for managing server state, including query invalidation, background data synchronization, and fine-grained control over the caching lifecycle.
- Full-featured caching solution
- Query invalidation and refetching
- Built-in support for pagination and infinite scrolling
- DevTools for debugging query state
- Mutation handling (for POST, PUT, DELETE requests)
SWR (Stale While Revalidate) is a React hook library developed by Vercel. The name refers to the data fetching strategy where stale data is shown while revalidating in the background. SWR is minimal and offers a simple API for fetching and caching remote data.
- Stale-While-Revalidate strategy
- Automatic caching and background revalidation
- Focus and network reconnection auto-fetching
- Simple API (based on hooks)
- Caching and Revalidation SWR follows the Stale-While-Revalidate pattern, where it shows stale data and revalidates it in the background. This is great for scenarios where you want fast updates without forcing the user to wait. React Query, on the other hand, offers more fine-grained control over caching, allowing you to define custom cache times, query invalidation, and refetching intervals.
- API Simplicity SWR is simpler to use with fewer configurations. If your goal is to have minimal setup and effortless caching, SWR is the way to go. React Query provides more options out of the box for handling complex use cases like pagination, mutations, and caching rules. If you need more control over server data management, React Query’s API is more feature-rich.
- Mutations and Side Effects React Query excels at handling mutations (for POST, PUT, DELETE requests), offering built-in support for optimistic updates, error handling, and rollback mechanisms. SWR focuses primarily on data fetching, leaving mutation logic to be implemented manually. However, you can pair SWR with external libraries for mutation handling.
- DevTools React Query comes with DevTools that make it easy to monitor queries, their statuses, cache contents, and refetch cycles in development mode. SWR does not have built-in DevTools, which can make debugging a bit more challenging in comparison.
- Performance and Background Fetching Both SWR and React Query offer features like background refetching on network reconnection or page focus, ensuring that your data stays fresh without manual intervention. React Query tends to offer more performance customization options, like setting stale times, refetch intervals, and even disabling background refetching entirely.
Both SWR and React Query are excellent solutions for data fetching in React, but which one should you choose?
- Choose SWR if: You need a simple, minimalistic solution for fetching and caching data, with a focus on the Stale-While-Revalidate pattern.
- Choose React Query if: You need more advanced data management features like query invalidation, paginated queries, mutation handling, or DevTools for debugging.
import { useQuery } from 'react-query';
const fetchUsers = async (page) => {
const res = await fetch(`/api/users?page=${page}`);
return res.json();
};
function UsersList({ page }) {
const { data, isLoading, isError } = useQuery(['users', page], () => fetchUsers(page));
if (isLoading) return <div>Loading...</div>;
if (isError) return <div>Error fetching users</div>;
return (
<div>
{data.users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}import useSWR from 'swr';
const fetcher = (url) => fetch(url).then(res => res.json());
function UsersList() {
const [page, setPage] = React.useState(1);
const { data, error } = useSWR(`/api/users?page=${page}`, fetcher);
if (error) return <div>Error fetching users</div>;
if (!data) return <div>Loading...</div>;
return (
<div>
{data.users.map(user => (
<div key={user.id}>{user.name}</div>
))}
<button onClick={() => setPage(page + 1)}>Load More</button>
</div>
);
}Handling loading, error, retry, caching, abort controllers, and refetching logic must all be implemented manually when using useEffect.
With useEffect, small mistakes in dependency arrays can trigger multiple or even infinite requests. SWR and React Query manage fetch lifecycles internally and prevent unnecessary calls.
Fetched data is cached and reused, making your UI faster and reducing server load. With useEffect, every navigation or re-render triggers a fresh request unless you write custom caching logic.
Your UI can display stale data instantly while new data is fetched silently in the background. This behavior requires no extra setup in SWR/React Query, but is cumbersome with useEffect.
When the user returns to the tab or regains network connection, data updates automatically. Implementing this manually with useEffect is complex, whereas SWR and React Query do it out-of-the-box.
Fetching data with useEffect leads to verbose, repetitive logic. SWR and React Query reduce the same functionality to a single declarative hook.
Fetching with useEffect (verbose):
useEffect(() => {
let isMounted = true;
setLoading(true);
fetch("/api/users")
.then((res) => res.json())
.then((data) => {
if (isMounted) {
setUsers(data);
setLoading(false);
}
})
.catch((err) => {
if (isMounted) {
setError(err);
setLoading(false);
}
});
return () => {
isMounted = false;
};
}, []);Fetching with React Query
const { data, error, isLoading } = useQuery(["users"], fetchUsers);Fetching with SWR
const { data, error, isLoading } = useSWR("/api/users", fetcher);