React Query: Caching, Background Sync, Pagination

In this module, we will explore React Query, a powerful data-fetching and state management library for React that simplifies handling asynchronous data. React Query offers various features like caching, background syncing, and pagination, which are essential when working with APIs in modern web applications.

We will dive deep into these features and demonstrate how to use React Query effectively to improve the performance and UX of your applications.


Table of Contents

  1. What is React Query?
  2. Setting Up React Query in Your React App
  3. Understanding Caching in React Query
  4. Background Sync in React Query
  5. Implementing Pagination with React Query
  6. Best Practices for Using React Query
  7. Conclusion

1. What is React Query?

React Query is a data-fetching and state management library that simplifies handling asynchronous operations in React applications. It is built on top of Promises and provides a simple API for fetching, caching, and synchronizing data.

Some of the key features of React Query include:

  • Automatic caching of data to avoid redundant API calls.
  • Background synchronization to keep the data fresh without requiring manual re-fetching.
  • Pagination and Infinite scrolling support for handling large datasets.
  • Automatic retries and error handling.

By using React Query, developers can improve the UX of their applications with less boilerplate code for managing data fetching, state management, and caching.


2. Setting Up React Query in Your React App

Before we dive into the advanced features of React Query, let’s first set it up in a React app.

Installing React Query

To install React Query, you can use npm or yarn:

bashCopyEditnpm install @tanstack/react-query

Setting up React Query Provider

React Query needs to be wrapped around the root component of your app using the QueryClientProvider to manage the query client globally.

javascriptCopyEditimport React from 'react';
import ReactDOM from 'react-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './App';

const queryClient = new QueryClient();

ReactDOM.render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>,
  document.getElementById('root')
);

3. Understanding Caching in React Query

One of the core features of React Query is caching. Caching helps avoid redundant API calls by storing the results of previous queries in memory. When the same data is requested again, React Query uses the cached data instead of making another network request.

How Caching Works

  • When you use React Query’s useQuery hook to fetch data, it automatically caches the response.
  • React Query uses the query key (a unique identifier for each query) to manage cache data.
  • By default, data in the cache is considered fresh for 5 minutes, after which it becomes stale and is eligible for re-fetching.

Example: Using useQuery with Caching

javascriptCopyEditimport React from 'react';
import { useQuery } from '@tanstack/react-query';

const fetchPosts = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts');
  return response.json();
};

function PostList() {
  const { data, isLoading, isError } = useQuery(['posts'], fetchPosts);

  if (isLoading) return <div>Loading...</div>;
  if (isError) return <div>Error loading posts</div>;

  return (
    <div>
      <h1>Post List</h1>
      <ul>
        {data.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

export default PostList;

Cache Invalidation

You can invalidate the cache manually using the queryClient.invalidateQueries method. This can be useful when you want to refetch data after a mutation, for example.

javascriptCopyEditqueryClient.invalidateQueries(['posts']);

4. Background Sync in React Query

Background syncing allows React Query to automatically keep data fresh by refetching it in the background when the data becomes stale or when certain conditions are met.

Automatic Background Sync

React Query uses stale time and refetch intervals to control background syncing.

  • Stale Time: The amount of time before the data becomes stale and needs to be refetched.
  • Refetch Interval: The interval at which React Query automatically fetches data in the background.

Example: Refetching Data in the Background

javascriptCopyEditconst { data } = useQuery(
  ['posts'],
  fetchPosts,
  {
    staleTime: 10000, // Data is fresh for 10 seconds
    refetchInterval: 60000, // Refetch data every minute
  }
);

Manual Background Sync

You can also manually trigger a background refetch using the refetch function that React Query provides.

javascriptCopyEditconst { data, refetch } = useQuery(['posts'], fetchPosts);

// Call refetch to reload data in the background
refetch();

5. Implementing Pagination with React Query

Pagination is a common requirement when dealing with large datasets. React Query simplifies this by allowing you to manage paginated data easily.

Using useQuery for Pagination

React Query doesn’t have built-in pagination support, but it can be easily implemented by passing the page number as a query variable.

Example: Paginated Posts

javascriptCopyEditconst fetchPaginatedPosts = async (page) => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=10`);
  return response.json();
};

function PaginatedPostList() {
  const [page, setPage] = React.useState(1);
  
  const { data, isLoading, isError, isPreviousData } = useQuery(
    ['posts', page],
    () => fetchPaginatedPosts(page),
    {
      keepPreviousData: true, // Keep old data while new data is being loaded
    }
  );

  if (isLoading) return <div>Loading...</div>;
  if (isError) return <div>Error loading posts</div>;

  return (
    <div>
      <h1>Paginated Post List</h1>
      <ul>
        {data.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
      <button
        onClick={() => setPage((old) => Math.max(old - 1, 1))}
        disabled={page === 1 || isPreviousData}
      >
        Previous
      </button>
      <button
        onClick={() => setPage((old) => (!isPreviousData ? old + 1 : old))}
        disabled={isPreviousData}
      >
        Next
      </button>
    </div>
  );
}

export default PaginatedPostList;

Key Points for Pagination:

  • keepPreviousData: Prevents the UI from flickering by keeping the previous page’s data while fetching new data.
  • You can pass dynamic variables, like page or limit, as part of the query key.
  • React Query will cache each page separately, ensuring that once a page is fetched, it doesn’t need to be re-fetched until the cache expires.

6. Best Practices for Using React Query

  • Use useQuery for server-side data: Always use useQuery for reading data from the server as it automatically handles caching, error handling, and synchronization.
  • Leverage Pagination: For large datasets, implement pagination or infinite scrolling to keep the app performant.
  • Prefetching: Use React Query’s prefetching capabilities to load data ahead of time (e.g., when navigating to a new page).
  • Error Handling: Always provide error handling using the onError callback to give users feedback on failed requests.
  • Optimistic Updates: Use optimistic updates for smooth UX when performing mutations (e.g., creating or deleting data).

7. Conclusion

In this module, we explored React Query, a powerful tool for simplifying data fetching and state management in React applications. We covered important concepts such as caching, background syncing, and pagination, which are essential for building performant and scalable React apps.

By integrating React Query into your projects, you can reduce boilerplate code, improve performance, and enhance the overall user experience when dealing with external data.