How React Query Works in React Applications: Simplify Data Fetching and Management

Introduction: What is React Query?

React Query is a powerful library that simplifies data fetching, caching, synchronization, and state management in React applications. As React applications grow more complex, managing server-side data (like fetching, caching, and updating data) becomes challenging. React Query abstracts these complexities and provides a declarative API for handling server-side data more efficiently.

In this article, we’ll explore how React Query works in React applications and how it can help developers manage data fetching in a cleaner, more maintainable way.


Why Use React Query in Your React Application?

Managing remote data in React applications traditionally involves using React state or Redux. While these approaches work, they can become cumbersome, especially for data fetching, caching, and synchronization with the server. React Query provides an elegant solution with the following benefits:

  1. Automatic Caching: React Query caches fetched data, ensuring it isn’t refetched unnecessarily.
  2. Background Fetching: Data is kept up-to-date by automatically refetching in the background.
  3. Simplified Data Management: React Query eliminates the need for manual state updates, reducing boilerplate code.
  4. Error Handling: React Query provides built-in support for handling errors and loading states.
  5. Pagination and Infinite Scrolling: React Query simplifies handling complex data requirements, such as pagination or infinite scrolling.

With React Query, you get more powerful features and a simplified API, making it the go-to solution for managing remote data in React.


How React Query Works in a React Application

React Query provides hooks like useQuery, useMutation, and useQueryClient to manage data fetching and mutation in a React component. Here’s a breakdown of how React Query functions in an application.

1. Setting Up React Query

To start using React Query, you first need to install it in your React project:

npm install react-query

Once installed, wrap your application with the QueryClientProvider and initialize a QueryClient. This is typically done in your App.js or index.js file.

import React from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import App from './App';

const queryClient = new QueryClient();

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

export default Main;

This setup ensures that your entire application has access to the React Query client, allowing you to perform data fetching, mutation, and caching seamlessly.

2. Using the useQuery Hook for Data Fetching

The useQuery hook is the core of React Query for fetching data. You provide a query key (unique identifier for the request) and a fetching function that returns the data. React Query automatically handles caching, background data fetching, and updating the component when new data is available.

Here’s an example of using the useQuery hook to fetch data from an API:

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

function fetchPosts() {
return fetch('https://jsonplaceholder.typicode.com/posts')
.then((res) => res.json());
}

function Posts() {
const { data, isLoading, error } = useQuery('posts', fetchPosts);

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

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

export default Posts;

In this example:

  • useQuery('posts', fetchPosts) fetches data from the API and caches it using the ‘posts’ key.
  • The query status (loading, error, or success) is tracked, and the component renders accordingly.

3. Handling Mutations with useMutation

In addition to data fetching, React Query also simplifies mutations, such as creating, updating, or deleting data. The useMutation hook helps you manage side-effects, handle loading and error states, and update the server-side data efficiently.

Here’s an example of how to use useMutation to create a new post:

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

function createPost(newPost) {
return fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: JSON.stringify(newPost),
headers: { 'Content-Type': 'application/json' }
})
.then((res) => res.json());
}

function CreatePost() {
const queryClient = useQueryClient();
const mutation = useMutation(createPost, {
onSuccess: () => {
// Invalidate and refetch posts after a successful mutation
queryClient.invalidateQueries('posts');
}
});

const handleSubmit = () => {
const newPost = { title: 'New Post', body: 'This is a new post' };
mutation.mutate(newPost);
};

return (
<div>
<button onClick={handleSubmit}>
{mutation.isLoading ? 'Adding Post...' : 'Add Post'}
</button>
{mutation.isError && <div>Error adding post</div>}
</div>
);
}

export default CreatePost;

Here:

  • useMutation(createPost) handles the POST request for creating a new post.
  • On successful mutation, queryClient.invalidateQueries('posts') is used to refetch and update the cached posts data.

4. Caching and Background Data Fetching

React Query’s caching and background refetching make it an excellent choice for performance optimization. When you use useQuery, React Query caches the result and avoids refetching data unless necessary. It also allows background fetching to keep the data up-to-date without affecting the user interface.

  • Stale Time: The period during which the cached data is considered fresh and will not be refetched.
  • Refetching: You can configure React Query to automatically refetch data on certain conditions (e.g., window focus or interval-based fetching).

For example:

const { data } = useQuery('posts', fetchPosts, {
staleTime: 5000, // Data will be fresh for 5 seconds
refetchOnWindowFocus: true, // Refetch data when the window regains focus
});

5. Pagination and Infinite Query

React Query makes it easy to implement pagination or infinite scrolling using the useInfiniteQuery hook. You can fetch data in pages and load additional data as the user scrolls or interacts with your application.

Here’s a basic example:

import { useInfiniteQuery } from 'react-query';

function fetchPostsPage({ pageParam = 1 }) {
return fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam}&_limit=10`)
.then((res) => res.json());
}

function PaginatedPosts() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery('posts', fetchPostsPage, {
getNextPageParam: (lastPage, allPages) => {
if (lastPage.length === 0) return undefined;
return allPages.length + 1;
}
});

return (
<div>
<h1>Paginated Posts</h1>
<ul>
{data.pages.map((page, index) => (
<li key={index}>{page.map(post => <p key={post.id}>{post.title}</p>)}</li>
))}
</ul>
{hasNextPage && !isFetchingNextPage && (
<button onClick={() => fetchNextPage()}>Load More</button>
)}
</div>
);
}

Best Practices for Using React Query

  1. Use Query Keys Effectively: Query keys are essential for caching and refetching. Make sure to use unique keys for different queries.
  2. Handle Errors Gracefully: Always account for loading, error, and success states in your components.
  3. Leverage Background Fetching: Use the built-in refetching features to keep your data up-to-date.
  4. Avoid Over-fetching: Use staleTime to avoid refetching data unnecessarily.

Conclusion

React Query simplifies data fetching and state management in React applications, providing a declarative and efficient way to manage server-side data. By using hooks like useQuery, useMutation, and useInfiniteQuery, you can improve performance, reduce boilerplate, and handle background fetching seamlessly. React Query also offers powerful features like caching, pagination, and automatic error handling, making it a must-have tool for React developers.

You may also like...