React Query Basics
React Query (now TanStack Query) is a powerful library for data fetching, caching, synchronization, and updating server state in React applications. It simplifies many common data fetching patterns and provides a great developer experience. This document covers the basics of getting started with React Query.
Why Use React Query?
Before diving into the code, let's understand why React Query is beneficial:
- Simplified Data Fetching: Handles fetching, caching, background updates, and stale-while-revalidate strategies with minimal boilerplate.
- Automatic Caching: Caches data automatically, reducing redundant API calls and improving performance.
- Background Updates: Automatically refetches data in the background to keep it fresh.
- Deduping: Avoids multiple identical requests.
- Optimistic Updates: Allows you to update the UI immediately and then sync with the server in the background.
- Pagination & Infinite Scrolling: Provides utilities for handling large datasets efficiently.
- Devtools: Excellent devtools for inspecting and debugging queries.
Installation
Install React Query using npm or yarn:
npm install @tanstack/react-query
# or
yarn add @tanstack/react-query
Basic Usage: useQuery
The core of React Query is the useQuery hook. It takes a query key and a query function as arguments.
- Query Key: A unique identifier for the query. This is typically an array of strings or numbers. React Query uses the query key for caching and re-fetching.
- Query Function: An asynchronous function that fetches the data. This function should return a promise that resolves with the data.
import { useQuery } from '@tanstack/react-query';
async function fetchData(key) {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${key[0]}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
}
function MyComponent() {
const { data, isLoading, isError, error } = useQuery(['todo', 1], fetchData);
if (isLoading) {
return <div>Loading...</div>;
}
if (isError) {
return <div>Error: {error.message}</div>;
}
return (
<div>
<h1>Todo</h1>
<p>Title: {data.title}</p>
<p>Completed: {data.completed ? 'Yes' : 'No'}</p>
</div>
);
}
export default MyComponent;
Explanation:
useQuery(['todo', 1], fetchData): This line initiates the query.['todo', 1]is the query key. We're fetching a specific todo item with ID 1. Using an array allows for more complex keys (e.g.,['todos', { page: 2, limit: 10 }]).fetchDatais the function that actually fetches the data.
data,isLoading,isError,error:useQueryreturns an object with several properties:data: The fetched data. Initiallyundefineduntil the data is loaded.isLoading: A boolean indicating whether the data is currently being fetched.isError: A boolean indicating whether an error occurred during the fetch.error: The error object if an error occurred.
Conditional Rendering: The component renders different content based on the
isLoading,isError, anddatastates.
Query Key Best Practices
- Use Arrays: Arrays are preferred for query keys. They allow you to include multiple dependencies.
- Serialization: Query keys should be serializable (e.g., strings, numbers, booleans, plain objects). Avoid using functions or complex objects directly.
- Specificity: Make your query keys specific enough to differentiate between different data requests.
Query Options
useQuery accepts an optional third argument: an options object. This object allows you to customize the behavior of the query.
const { data, isLoading, isError, error } = useQuery(
['todos'],
fetchTodos,
{
refetchOnWindowFocus: false, // Don't refetch when the window gains focus
staleTime: 60 * 1000, // Data is considered stale after 1 minute
cacheTime: 5 * 60 * 1000, // Data remains in the cache for 5 minutes
retry: 3, // Retry the request up to 3 times
onError: (error) => {
console.error('Error fetching todos:', error);
},
}
);
Common Query Options:
refetchOnWindowFocus: Whether to refetch data when the window gains focus. Defaults totrue.refetchOnMount: Whether to refetch data when the component mounts. Defaults totrue.staleTime: How long data is considered "fresh" before React Query attempts to refetch it in the background. Defaults to0(always stale).cacheTime: How long data remains in the cache after the last component unmounts. Defaults to5 minutes.retry: The number of times to retry the request if it fails. Defaults to3.onError: A callback function that is called when an error occurs.onSuccess: A callback function that is called when the data is successfully fetched.enabled: A boolean that determines whether the query should be enabled. Useful for conditional fetching.select: A function that transforms the data before it's returned to the component.
The QueryClient
The QueryClient is a central object that manages the cache and provides methods for interacting with the query cache.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<MyComponent />
</QueryClientProvider>
);
}
Explanation:
new QueryClient(): Creates a newQueryClientinstance.QueryClientProvider: Wraps your application with theQueryClientProvidercomponent, making theQueryClientavailable to all components that use React Query.
Accessing the QueryClient
You can access the QueryClient instance using the useQueryClient hook:
import { useQueryClient } from '@tanstack/react-query';
function MyComponent() {
const queryClient = useQueryClient();
const invalidateQuery = () => {
queryClient.invalidateQueries(['todo', 1]); // Mark the query as stale
};
return (
<div>
<button onClick={invalidateQuery}>Invalidate Query</button>
</div>
);
}
Common QueryClient Methods:
getQueryData(queryKey): Retrieves the cached data for a given query key.setQueryData(queryKey, data): Updates the cached data for a given query key.invalidateQueries(queryKey): Marks a query (or queries) as stale, triggering a refetch on the next access.refetchQueries(queryKey): Immediately refetches a query (or queries).removeQueries(queryKey): Removes queries from the cache.
This provides a foundational understanding of React Query. Further exploration of features like mutations, pagination, and devtools will unlock even more powerful data management capabilities in your React applications.