Tiny Frontend Logo
Posts 0991

On This Page

React-Query Data Fetching

React Query, also known as TanStack Query, is a powerful library designed to simplify data fetching, caching, and state management in React applications.

As reported by TanStack, it excels at fetching, caching, synchronizing, and updating server state, addressing limitations in traditional state management libraries when dealing with asynchronous or server-side data.

Asynchronous State Management Simplified

As React applications grow in complexity, managing state—especially asynchronous server-side data—becomes increasingly challenging. Traditional state management tools, while powerful, often require extensive boilerplate code and can struggle with handling asynchronous data fetching, caching, and synchronization effectively. This is where React Query comes into play.

React Query is designed to address these challenges, offering significant advantages over conventional data fetching methods. Here's why developers often choose React Query:

  1. Simplified data fetching: React Query provides an intuitive API for fetching data from RESTful or GraphQL APIs, drastically reducing boilerplate code and improving developer productivity.
  2. Intelligent caching: The library implements automatic caching strategies, improving performance by minimizing unnecessary network requests and providing faster access to data.
  3. Automatic background updates: React Query can refresh data in the background, ensuring that users always see the most up-to-date information without manual intervention.
  4. Built-in error handling: The library offers robust error handling and retry capabilities out of the box, making it easier to build resilient applications.

Project Integration Workflow

To start using React Query in your project, follow these steps:

Installation

Begin by installing React Query and its peer dependencies using npm or yarn.

npm install @tanstack/react-query
# or
yarn add @tanstack/react-query

QueryClient Setup:

Create a QueryClient instance and wrap your application with QueryClientProvider.

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

// create the client
const queryClient = new QueryClient()

function App() {
  // Provide the client to your App
  return <QueryClientProvider client={queryClient}>
    <Todos />
  </QueryClientProvider>;
}

Define Queries:

Create custom hooks for your queries using the useQuery hook.

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

export function useTodos() {
  return useQuery({
    queryKey: ['todos'],
    queryFn: () => {
      return fetch('/api/todos').then(res => res.json())
    }
  });
}

queryKey is a unique identifier for your query, acting as a key to manage cached data. You can use strings, arrays, or objects to create distinct queryKeys.

queryFn is the function responsible for fetching data from your API or data source. It accepts no arguments and should return a promise that resolves with the fetched data.

Use Queries in Components

Implement the custom hooks in your components

// import querie hooks
import { useTodos } from './queries';

function Todos() {
  // get data and status
  const { isPending, error, data } = useTodos();

  // render loading
  if (isPending) {
    return renderLoading();
  }
  // render error
  if (error) {
    return 'An error has occurred: ' + error.message;
  }
  // render content
  return renderContent();
}

Temporal Data Persistence

React Query implements an intelligent caching system that stores query results in memory, reducing unnecessary network requests and providing instant data access.

Data Caching

The cache is based on unique query keys, allowing for fine-grained control over data invalidation and updates. The caching mechanism employs a stale-while-revalidate strategy, where cached data is immediately returned while a background refetch occurs to ensure data freshness. This approach is particularly effective for read-heavy workloads, as it balances data consistency with performance.

The cache can be customized using options like staleTime and cacheTime to control how long data remains fresh and how long it persists in the cache, respectively.

For example:

const { data } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  // Data remains fresh for 1 minute
  staleTime: 60000,
  // Cached data persists for 15 minutes
  cacheTime: 900000
});

Query Invalidation

Simply waiting for queries to become stale before refetching them isn't always reliable, particularly when you know the data is outdated due to user actions. To handle this, the QueryClient offers an invalidateQueries method, allowing you to smartly mark queries as stale and trigger a refetch if needed.

Query invalidation can be performed with varying levels of specificity, from broad invalidation of all queries to precise targeting of specific query keys.

// Invalidate all queries in the QueryClient
// Marking them as stale and potentially triggering a refetch
queryClient.invalidateQueries();

// Invalidate all queries with the 'todos' queryKey
// Marking them as stale and potentially triggering a refetch
queryClient.invalidateQueries({ queryKey: ['todos'] });

// Invalidate only the specific query with the exact 'todos' queryKey
// Marking it as stale and potentially triggering a refetch
queryClient.invalidateQueries({ queryKey: ['todos'], exact: true });

Request Waterfalls and useSuspenseQueries

React Query, being designed for optimal data fetching and caching, often encounters situations where dependent queries rely on data from previous queries. To manage these scenarios efficiently, it introduces the concept of request waterfalls and provides the powerful useSuspenseQueries hook.

Imagine a scenario where you need to fetch a list of users and then, for each user, retrieve their associated posts. Traditional approaches might involve nested promises or callbacks, leading to complex code and potential performance issues.

React Query simplifies this process with request waterfalls, allowing you to chain dependent queries together, ensuring they execute in the correct order and only after their predecessors have successfully completed.

This is where useSuspenseQueries comes into play. It provides a clean and declarative way to define these waterfalls, enabling React Query to automatically handle the dependencies and manage the state of the queries in a streamlined manner.

Here's how useSuspenseQueries works:

  1. Define Your Queries: First, define your individual queries for fetching users and posts using useQuery.
  2. Specify Dependencies: Within useSuspenseQueries, define your queries and declare their dependencies. For example, the posts query might depend on the users query.
  3. Automatic Management: React Query automatically handles the execution order and manages the state of each query, ensuring that dependent queries are triggered only when their prerequisites are resolved.
import { useQuery } from '@tanstack/react-query';
import { useSuspenseQueries } from '@tanstack/react-query';

// Define the user query
const usersQuery = () => useQuery({
  queryKey: ['users'],
  queryFn: () => fetch('/api/users').then(res => res.json()),
});

// Define the posts query
const postsQuery = (userId) => useQuery({
  queryKey: ['posts', userId],
  queryFn: () => fetch(`/api/posts/${userId}`).then(res => res.json()),
});

// Use useSuspenseQueries to define the waterfall
const { data: users } = usersQuery();
const { data: posts, isLoading: isPostsLoading } = useSuspenseQueries(
  users?.map(user => {
    return postsQuery(user.id);
  }),
  // Optional config for managing the fallback experience
  {
    refetchOnWindowFocus: false,
    suspense: true,
  }
);

// Render based on the query state
return (
  <div>
    {isPostsLoading && <p>Loading posts...</p>}
    {posts && posts.map(post => (
      <div key={post.id}>
        <h3>{post.title}</h3>
        <p>{post.body}</p>
      </div>
    ))}
  </div>
);

This code demonstrates how useSuspenseQueries orchestrates the fetching of users and their associated posts. The postsQuery depends on the usersQuery, ensuring that the posts are fetched only after the users are successfully retrieved. The suspense: true option ensures that React Query suspends the rendering of the component until all dependent queries have completed.

useSuspenseQueries empowers you to build complex data-driven applications by providing a simple and elegant way to manage dependent queries, preventing race conditions and ensuring data consistency. It's a powerful addition to the React Query toolkit, simplifying the handling of data dependencies and delivering a smooth user experience.

Retry and Retry Delay

Network connectivity issues, server outages, or other unforeseen circumstances can sometimes interrupt data fetching. To handle these scenarios gracefully and ensure a resilient user experience, React Query provides built-in retry mechanisms with customizable delay settings.

Automatic Retries

By default, React Query automatically retries failed queries. This behavior is controlled by the retry option in the useQuery configuration. By setting retry: true, React Query will attempt to re-execute the query a set number of times if an error occurs, improving the chance of a successful data fetch.

const { data, error, isError } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  // Retry the query 3 times if an error occurs
  retry: 3,
  retryDelay: 1000,
});

// Render based on query state
if (isError) {
  return <p>An error occurred: {error.message}</p>;
}
// Render loading state
if (isLoading) {
  return <p>Loading...</p>;
}
// Render the data
return <ul>{data.map(todo => <li key={todo.id}>{todo.title}</li>)}</ul>;

Custom Retry Delays

The retryDelay option allows you to control the delay between retry attempts, enabling you to fine-tune the retry strategy based on the expected error frequency and severity. A longer delay might be suitable for intermittent issues, while a shorter delay may be preferred for persistent errors.

// Wait 2 seconds before retrying
const { data, error, isError } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  retryDelay: 2000,
});

Conclusion

React Query truly simplifies and enhances data fetching and management in React applications. Its intuitive API, combined with its powerful caching and background update mechanisms, significantly reduces boilerplate code and improves performance.

We've explored how to integrate React Query into your project, setting up the QueryClient, defining queries, and using useQuery to access and manage data in your components. We've also delved into advanced features like request waterfalls, retries, and retry delays, enabling you to handle complex scenarios and build resilient data-driven applications.

For further exploration and a deeper dive into React Query's extensive feature set, visit its official website: https://tanstack.com/query/. You'll find comprehensive documentation, examples, and tutorials to help you master this powerful library and unlock its full potential for your React projects.

Read Next

0139

Build a Tile-Based Google Map with JavaScript

Ever wondered how Google Maps loads and zooms so smoothly? While their full system is massively complex, the fundamental idea behind map display is surprisingly approachable.

Build a Tile-Based Google Map with JavaScript