How to Optimize React Query in React

React Query (now TanStack Query) is an industry-standard library for managing asynchronous state in React applications. While it offers excellent default configurations, scaling your application requires deliberate optimization to prevent redundant network requests, reduce CPU overhead, and eliminate unnecessary component re-renders. This article provides a highly practical guide to optimizing React Query by adjusting cache lifetimes, utilizing selectors, implementing efficient pagination, and fine-tuning global configurations.

1. Adjust staleTime and gcTime

By default, React Query sets staleTime to 0. This means every time a component mounts or a window regains focus, React Query marks the data as stale and triggers a background refetch.

To optimize performance and reduce server load, increase the staleTime for data that does not change frequently:

const { data } = useQuery({
  queryKey: ['userProfile'],
  queryFn: fetchUserProfile,
  staleTime: 1000 * 60 * 5, // Data remains fresh for 5 minutes
  gcTime: 1000 * 60 * 10,   // Garbage collect cache after 10 minutes
});

2. Prevent Unnecessary Re-renders with Selectors

When a query updates, every component subscribing to that query re-renders by default. You can optimize this behavior by using the select option to subscribe to a specific subset of the fetched data. The component will only re-render if the selected slice of data changes.

const { data: userEmail } = useQuery({
  queryKey: ['user'],
  queryFn: fetchUser,
  select: (user) => user.email, // Component only re-renders if the email changes
});

3. Disable Default Refetching Behaviors

React Query automatically triggers background refetches on window focus, reconnect, or query mount. While useful, these default settings can cause redundant API calls in highly interactive applications. You can disable them globally or on a per-query basis:

const { data } = useQuery({
  queryKey: ['staticData'],
  queryFn: fetchStaticData,
  refetchOnWindowFocus: false, // Disable refetch when user switches tabs
  refetchOnMount: false,       // Disable refetch when component remounts
  refetchOnReconnect: false,   // Disable refetch on network reconnect
});

4. Implement Smooth Pagination with placeholderData

When implementing pagination, switching pages normally triggers a loading state, resulting in a flickering user interface. To optimize the user experience, use the placeholderData option to keep the previous page’s data visible while the new page loads.

import { keepPreviousData, useQuery } from '@tanstack/react-query';

const { data, isPlaceholderData } = useQuery({
  queryKey: ['projects', page],
  queryFn: () => fetchProjects(page),
  placeholderData: keepPreviousData, // Keeps old data visible during the fetch
});

5. Prefetch Data Before Users Click

Prefetching is a highly effective optimization technique that fetches data before it is rendered on the screen. You can prefetch data when a user hovers over a link, button, or navigation element.

const queryClient = useQueryClient();

const prefetchProjectDetails = async (projectId) => {
  await queryClient.prefetchQuery({
    queryKey: ['project', projectId],
    queryFn: () => fetchProject(projectId),
    staleTime: 1000 * 60 * 5,
  });
};

// Trigger this function on a button's onMouseEnter event

6. Configure Global Defaults

Instead of optimizing each query individually, establish sensible global defaults when initializing the QueryClient. This ensures consistent performance optimization across your entire application codebase.

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 2, // 2 minutes
      refetchOnWindowFocus: false,
      retry: 1, // Limit retry attempts on failure to reduce network traffic
    },
  },
});