Blogs

How to Boost React Performance with React Query Caching?

How to Boost React Performance with React Query Caching?
Improve Data Fetching Efficiency with React Query

Introduction

In modern web applications, efficient data fetching and state management are critical for performance and user experience. React Query, a library popular for handling these aspects in React applications, simplifies data fetching, caching, and synchronization, making it a valuable tool for developers. This blog post explores how to use caching in React applications with React Query, discussing its benefits, use cases, and working principles.

Problem Statement

Data fetching in React applications can be tough. Challenges include redundant network requests, managing loading states, error handling, and keeping the UI in sync with the backend. Traditional methods using libraries like Redux or the Context API can be complex and result in repetitive code. Additionally, efficient data caching to reduce unnecessary requests and boost performance introduces further complexity.

Challenges in Traditional Data Fetching

  1. Redundant Network Requests: Without appropriate caching, applications can make repeated unnecessary network requests for identical data. This results in longer load times and increased server expenses.
  2. Complexity of State Management: The management of loading, error, and success states for several asynchronous operations can result in complex code that is difficult to maintain.
  3. Synchronization Issues: Maintaining the UI's synchronization with the latest backend data can be challenging, particularly in applications that often update or have multiple data sources.
  4. Performance Bottlenecks: Applications could face performance issues without efficient caching. This is especially true under heavy load or when data fetching is a frequent operation.

Use Case

Imagine you are creating a dashboard that presents user data, which includes profile information, recent activities, and statistics. Each segment of the dashboard needs to retrieve data from various endpoints. Without suitable caching, the application would send unnecessary requests to the server each time a user visits the dashboard. This increases load times and server expenses. By implementing caching, you can greatly enhance performance and user experience through the reuse of previously retrieved data.

Specific Use Case Example

Consider a social media application where users can view their profile, posts, followers, and messages. Each section requires separate data fetching:

  • Profile Information: This includes basic user details like name, profile picture, and bio.
  • Recent Posts: This is a list of posts made by the user.
  • Followers: This is a list of users who follow the current user.
  • Messages: These are direct messages sent to and received by the user.

In a conventional setup, navigating between these sections could lead to multiple redundant API calls, particularly if the user frequently switches between views. This impacts performance and leads to a less-than-ideal user experience. By caching these responses, we can serve data from the cache when available, reducing the need for repeated network requests.

Connect with us to get the best React developers

How React Query Works

1. Installation and Setup

To begin using React Query, you must first install it in your React project:

npm install react-query

Next, wrap your application using the QueryClientProvider to provide the React Query context:

import { QueryClient, QueryClientProvider } from 'react-query';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <App /> 
    </QueryClientProvider>
  );
}

export default App;

2. Fetching Data with useQuery

React Query offers the useQuery hook for data fetching. This hook automates the management of caching, background updates, and stale data.

import { useQuery } from 'react-query';
import axios from 'axios';

const fetchUserData = async () => {
  const { data } = await axios.get('/api/user');
  return data;
};

function UserProfile() {
  const { data, error, isLoading } = useQuery('userData', fetchUserData);

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.email}</p>
    </div>
  );
}

3. Caching and Stale Time

React Query, by default, caches data for 5 minutes. You can customize this duration using the staleTime and cacheTime options:

  • staleTime: This is the duration during which data is considered fresh. React Query will not refetch the data within this period.
  • cacheTime: This is the duration for which inactive data remains in the cache before being garbage collected.
const { data, error, isLoading } = useQuery('userData',fetchUserData, {
  staleTime: 10000, // 10 seconds
  cacheTime: 300000, // 5 minutes
});

4. Invalidating and Refetching Data

React Query simplifies the process of refetching or invalidating data. For instance, following a mutation such as updating user data, you may need to refetch this data to keep the user interface synchronised:

import { useMutation, useQueryClient } from 'react-query';

const updateUser = async (userData) => {
  const { data } = await axios.put('/api/user', userData);
  return data;
};

function UpdateUserForm() {
  const queryClient = useQueryClient();
  const mutation = useMutation(updateUser, {
    onSuccess: () => {
      queryClient.invalidateQueries('userData');
    },
  });

  // Form submission handler
  const handleSubmit = (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    mutation.mutate({ name: formData.get('name'), email: formData.get('email') });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="Name" />
      <input name="email" placeholder="Email" />
      <button type="submit">Update</button>
    </form>
  );
}

5. Background Data Syncing

React Query provides support for background data syncing, which ensures your UI consistently reflects the most recent server data. This feature is particularly valuable for real-time applications or situations where data frequently changes.

For instance, you can set up automatic refetching at fixed intervals:

const { data, error, isLoading } = useQuery('userData', fetchUserData, {
  refetchInterval: 60000, // Refetch every 60 seconds
});

6. Pagination and Infinite Query

React Query supports paginated and infinite queries, simplifying the handling of large data sets efficiently.

Here is an example of a Paginated Query:

const fetchProjects = async (page) => {
  const { data } = await axios.get(`/api/projects?page=${page}`);
  return data;
};

function ProjectsList() {
  const [page, setPage] = useState(1);
  const { data, isLoading, isError } = useQuery(['projects', page], () => fetchProjects(page));

  if (isLoading) return <p>Loading...</p>;
  if (isError) return <p>Error loading projects</p>;

  return (
    <div>
      {data.projects.map((project) => (
        <div key={project.id}>{project.name}</div>
      ))}
      <button onClick={() => setPage((prev) => prev + 1)}>Next Page</button>
    </div>
  );
}

Here is an example of a Infinite Query

import { useInfiniteQuery } from 'react-query';

const fetchProjects = async ({ pageParam = 1 }) => {
  const { data } = await axios.get(`/api/projects?page=${pageParam}`);
  return data;
};

function InfiniteProjectsList() {
  const { data, fetchNextPage, hasNextPage, isFetching, isFetchingNextPage } = useInfiniteQuery(
    'projects',
    fetchProjects,
    {
      getNextPageParam: (lastPage) => lastPage.nextPage ?? false,
    }
  );

  return (
    <div>
      {data.pages.map((page) =>
        page.projects.map((project) => (
          <div key={project.id}>{project.name}</div>
        ))
      )}
      <button onClick={() => fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage}>
        {isFetchingNextPage ? 'Loading more...' : hasNextPage ? 'Load More' : 'Nothing more to load'}
      </button>
      {isFetching && !isFetchingNextPage ? 'Fetching...' : null}
    </div>
  );
}

How React Query Works Under the Hood

React Query utilises a query client, tasked with handling all queries and mutations in your application. Here is a detailed explanation of the main components and their interactions:

Query Client

The QueryClient is the heart of React Query. It maintains the cache and offers methods to manage queries and mutations. Upon creating a new QueryClient, you can set it up with default options for all queries and mutations:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 10000,
      cacheTime: 300000,
    },
  },
});

Query Cache

The query cache stores all query results. Each query is identified by a unique key, which is used to retrieve and update the data. When a query is fetched, React Query first checks if the data is in the cache and whether it's still fresh based on the staleTime setting. If the data is stale or not present, it fetches new data from the server.

Query Observer

Query observers monitor changes in queries and trigger re-renders in your components. When you use the useQuery hook, it creates a query observer. This observer subscribes to the query cache and updates the component state whenever the query's status or data changes.

Background Fetching and Staleness

React Query automatically fetches data in the background when a query becomes stale. This ensures that your UI always displays the most current information. The stale-time mechanism determines when a query's data is considered fresh or stale. During the stale period, React Query will serve data from the cache without fetching it again. Once the data becomes stale, it will fetch it in the background the next time it is accessed or based on a refetch interval if specified.

Mutation and Invalidation

Mutations in React Query are used for creating, updating, or deleting data. After a successful mutation, you often need to refetch or invalidate queries to ensure the UI reflects the latest data. The useMutation hook provides a way to perform mutations and includes callbacks for handling side effects, such as invalidating queries.

const mutation = useMutation(updateUser, {
  onSuccess: () => {
    queryClient.invalidateQueries('userData');
  },
});

Query Keys and Dependencies

Query keys play a crucial role in React Query because they uniquely identify each query. These keys can be either simple strings or complex arrays, which enables you to create dependent queries. For instance, fetching user posts may depend on the user ID.

const { data: user } = useQuery('user', fetchUser);
const { data: posts } = useQuery(['posts', user.id], fetchPosts);

Conclusion

React Query streamlines data fetching and caching in React applications, lessening the requirement for boilerplate code and offering a smooth developer experience. With its potent caching mechanisms, you can boost the performance and responsiveness of your applications, which enhances the user experience. Regardless of whether you're building a small app or a large-scale project, React Query provides a robust solution for effectively managing server state.

Implementing React Query provides you with:

  • Efficient Data Fetching: Reduce redundant network requests and enhance performance.
  • Automatic Caching: Instantly cache data to serve future requests faster.
  • Background Data Syncing: Keep your UI consistently updated with the latest data.
  • Simplified State Management: Lessen boilerplate code and manage asynchronous operations more effectively.

Faqs-

What is React Query and how does it help with data fetching in React applications?

React Query is a powerful library for managing server state in React applications. It simplifies data fetching, caching, synchronization, and background updating. By using React Query developers can reduce redundant network requests to manage loading and error states more efficiently. This keeps the UI synchronized with the latest backend data.

Why is caching important for React applications?

Caching is crucial because it reduces the number of unnecessary network requests which in turn decreases load times and server expenses. By serving data from the cache, applications can become more responsive and provide a better user experience.

How does the useQuery hook work in React Query?

The useQuery hook is used to fetch data. It automatically manages caching, background updates, and stale data, making the data fetching process more efficient and less repetitive.

What are staleTime and cacheTime in React Query?

Stale time is the duration during which the data is considered fresh. React Query will not refetch the data within this period.
cacheTime is the duration for which inactive data remains in the cache before being garbage collected.

How can I invalidate and refetch data in React Query?

React Query provides mechanisms to invalidate and refetch data, ensuring that the UI remains synchronized with the latest data after certain actions, such as updates.

How does React Query handle background data syncing?

React Query can automatically refetch data in the background at specified intervals, ensuring your UI consistently reflects the most recent server data.

What is the use of polling in React query?

In React Query, polling refers to the process of automatically refetching data at specified intervals. This is useful for real-time applications or scenarios where data may frequently change.