React Query – React

React Query is a powerful library for managing server-state in React applications. It simplifies the process of fetching, caching, synchronizing, and updating server data, reducing the need for complex state management logic and boilerplate code.

Why use React Query?

  • Server State Management: Server state refers to data that is fetched from a remote server. React Query makes it easy to manage this state, which can often be difficult due to its asynchronous nature and the need to keep the UI in sync with changing data.
  • Caching: React Query caches fetched data and automatically refetches it when needed (e.g., when stale).
  • Background Updates: It can refetch data in the background to keep the UI up-to-date.
  • Automated Garbage Collection: React Query automatically removes unused data from cache to optimize performance.
  • Out-of-the-box pagination and infinite scrolling: You can handle pagination with ease using React Query.

Installation

First, install React Query using npm or yarn:

npm install @tanstack/react-query

Setup

You need to wrap your app in a QueryClientProvider, which provides React Query with the necessary context.

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

// Create a client
const queryClient = new QueryClient();

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

export default Root;

Basic Usage Example

Let’s say you want to fetch a list of posts from an API. React Query provides the useQuery hook for fetching data.

Fetching Data

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

// Simulating an API fetch
const fetchPosts = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts');
  if (!response.ok) {
    throw new Error('Network response was not ok');
  }
  return response.json();
};

function Posts() {
  // The useQuery hook manages fetching and caching automatically
  const { data, error, isLoading } = useQuery(['posts'], fetchPosts);

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

  return (
    <ul>
      {data.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}
  • Key: 'posts' is the query key. React Query uses this to identify and cache data.
  • Loading State: The isLoading flag helps to display loading feedback while fetching data.
  • Error Handling: If something goes wrong, React Query provides an error object.
  • Data: When the request succeeds, data contains the fetched results.

Query Key Importance

The query key can be a simple string (as shown), or it can be an array containing other identifiers, which helps React Query know how to cache and manage the data. For instance, if you have a dynamic query that depends on a variable, you could pass an array like this:

const { data, isLoading } = useQuery(['posts', userId], () => fetchPostsByUserId(userId));

Refetching Data

You can manually refetch data using the refetch function provided by useQuery. Here’s an example:

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

return (
  <div>
    <button onClick={() => refetch()}>Refetch Posts</button>
    {isLoading ? <div>Loading...</div> : <ul>{data.map(post => <li key={post.id}>{post.title}</li>)}</ul>}
  </div>
);

Polling / Automatic Refetching

You can set the refetchInterval option to automatically refetch data every given interval (in milliseconds). This is useful for real-time data or data that frequently updates.

const { data, isLoading } = useQuery(['posts'], fetchPosts, {
  refetchInterval: 5000, // Refetch every 5 seconds
});

Stale and Cached Data

React Query allows you to mark data as “stale” or “fresh.” By default, fetched data is considered “stale” immediately, but you can control this using the staleTime option. For instance, if data is unlikely to change within 10 seconds:

const { data, isLoading } = useQuery(['posts'], fetchPosts, {
  staleTime: 10000, // Data will stay fresh for 10 seconds
});

Mutations (POST/PUT/DELETE)

React Query also provides a useMutation hook for handling POST, PUT, DELETE, or any operation that modifies data on the server. For example, to create a new post:

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

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

function CreatePost() {
  const queryClient = useQueryClient();
  const mutation = useMutation(createPost, {
    // Update the cache after a successful mutation
    onSuccess: () => {
      queryClient.invalidateQueries(['posts']);
    },
  });

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

  return (
    <div>
      <button onClick={handleSubmit}>
        {mutation.isLoading ? 'Creating...' : 'Create Post'}
      </button>
      {mutation.isError && <div>An error occurred</div>}
      {mutation.isSuccess && <div>Post created successfully!</div>}
    </div>
  );
}
  • useMutation: This hook is used to handle mutations (POST, PUT, DELETE).
  • queryClient.invalidateQueries(['posts']): This function forces React Query to refetch the posts after the mutation is successful, ensuring the UI is updated with the new post.

Pagination with React Query

React Query also makes handling pagination easy. Here’s an example of fetching paginated data:

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

function PaginatedPosts() {
  const [page, setPage] = React.useState(1);

  const { data, isLoading, isPreviousData } = useQuery(['posts', page], () => fetchPostsPaginated(page), {
    keepPreviousData: true, // Ensures smooth pagination without removing the previous data
  });

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

Summary

React Query simplifies the process of fetching and synchronizing server data in a React application by offering:

  • Simple data fetching and caching with the useQuery hook.
  • Powerful mutation handling with useMutation.
  • Built-in features like refetching, caching, pagination, and background updates.

It’s a great choice for improving the data-fetching experience in modern React apps, offering performance benefits and reducing boilerplate.

Tags: No tags

Add a Comment

Your email address will not be published. Required fields are marked *