React Query revolutionizes data fetching in React. Here's everything you need to know.
Setup
npm install @tanstack/react-query @tanstack/react-query-devtools
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
retry: 3,
},
},
});
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
Basic Queries
import { useQuery } from '@tanstack/react-query';
function Users() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: async () => {
const res = await fetch('/api/users');
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
},
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Mutations
import { useMutation, useQueryClient } from '@tanstack/react-query';
function CreateUser() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async (newUser: User) => {
const res = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(newUser),
});
return res.json();
},
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
return (
<button
onClick={() => mutation.mutate({ name: 'New User', email: 'new@example.com' })}
disabled={mutation.isPending}
>
{mutation.isPending ? 'Creating...' : 'Create User'}
</button>
);
}
Dependent Queries
function UserPosts({ userId }: { userId: string }) {
// Fetch user first
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
// Then fetch posts (only when user is available)
const { data: posts } = useQuery({
queryKey: ['posts', userId],
queryFn: () => fetchUserPosts(userId),
enabled: !!user, // Query won't run until user exists
});
return <PostList posts={posts} />;
}
Infinite Queries
import { useInfiniteQuery } from '@tanstack/react-query';
function InfiniteList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: async ({ pageParam = 1 }) => {
const res = await fetch(`/api/posts?page=${pageParam}`);
return res.json();
},
getNextPageParam: (lastPage, pages) => {
return lastPage.hasMore ? pages.length + 1 : undefined;
},
initialPageParam: 1,
});
return (
<>
{data?.pages.map((page, i) => (
<div key={i}>
{page.posts.map(post => (
<Post key={post.id} {...post} />
))}
</div>
))}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage ? 'Loading...' : 'Load More'}
</button>
</>
);
}
Optimistic Updates
const mutation = useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ['todos'] });
// Snapshot previous value
const previousTodos = queryClient.getQueryData(['todos']);
// Optimistically update
queryClient.setQueryData(['todos'], (old) => [...old, newTodo]);
// Return context with snapshot
return { previousTodos };
},
onError: (err, newTodo, context) => {
// Rollback on error
queryClient.setQueryData(['todos'], context.previousTodos);
},
onSettled: () => {
// Always refetch after error or success
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
Prefetching
async function prefetchData() {
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
staleTime: 10 * 1000,
});
}
// Prefetch on hover
<Link
href="/posts"
onMouseEnter={() => prefetchData()}
>
View Posts
</Link>
React Query simplifies data management and improves user experience significantly.
Posted onreactwith tags: