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:
- 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.
- Intelligent caching: The library implements automatic caching strategies, improving performance by minimizing unnecessary network requests and providing faster access to data.
- 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.
- 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:
- Define Your Queries: First, define your individual queries for fetching users and posts using
useQuery
. - Specify Dependencies: Within
useSuspenseQueries
, define your queries and declare their dependencies. For example, the posts query might depend on the users query. - 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.