Mastering the useQuery Hook in React Query: A Complete Guide

Inroduction
When it comes to data fetching in React, the useQuery hook from React Query has become a go-to solution for handling server-side state. It abstracts away much of the complexity of data fetching, caching, synchronization, and background updates, making it a powerful tool for React developers.
In this blog post, we’ll dive deep into the useQuery hook—explaining how it works, how to use it effectively, and common patterns you can leverage in your React applications.
What is useQuery?
The useQuery hook is a part of the React Query library, designed to help you fetch, cache, and manage data in your React applications with minimal effort. It’s specifically optimized for handling server-side data (e.g., data fetched from APIs or databases), making it much easier to handle common data-fetching scenarios, such as:
- Fetching data when the component mounts
- Automatically caching data
- Handling loading, error, and success states
- Background refetching for fresh data
With useQuery, you no longer need to worry about manually handling loading states, caching, or error handling. React Query manages this for you automatically.
How to Use useQuery
Using useQuery is straightforward. You’ll call it inside a functional component and pass it a unique key (query key) and a function to fetch the data (query function).
Here’s a basic example of using useQuery to fetch data from an API:
Example: Fetching Data with useQuery
import React from 'react';
import { useQuery } from 'react-query';
// Function to fetch data from the API
const fetchUserData = async () => {
const res = await fetch('https://api.example.com/user');
if (!res.ok) {
throw new Error('Network response was not ok');
}
return res.json(); // Return the fetched data
};
function UserProfile() {
const { data, error, isLoading } = useQuery('userData', fetchUserData)
// Handle loading state
if (isLoading) return <div>Loading...</div>;
// Handle error state
if (error) return <div>Error: {error.message}</div>;
// Render the fetched data
return (
<div>
<h1>User Profile</h1>
<p>Name: {data.name}</p>
<p>Email: {data.email}</p>
</div>
);
}
export default UserProfile;
In this example:
- ‘userData’ is the query key. This key is used to uniquely identify the query, and React Query will cache the results using this key. If the query is re-run with the same key, React Query will return the cached data.
- fetchUserData is the query function. This is an asynchronous function that returns the data you want to fetch. It can be an API call, or any async operation (like fetching data from a database).
- The object returned by useQuery contains several helpful properties, including:
- data: The fetched data.
- error: Any error that occurred during the fetch.
- isLoading: Boolean indicating if the query is currently loading.
- isError: Boolean indicating if there was an error fetching the data.
- isSuccess: Boolean indicating if the query was successful
Key Options in useQuery
useQuery comes with a variety of options that can be used to customize the behavior of your query. Here are some of the most commonly used options:
1. enabled – Conditionally Disable Query
You can use the enabled option to conditionally skip the query if certain conditions are not met (for example, only fetching data if the user is logged in).
const { data, isLoading, error } = useQuery(
'userData',
fetchUserData,
{ enabled: isLoggedIn } // Only run the query if user is logged in
);
2. refetchOnWindowFocus – Refetch Data on Window Focus
By default, React Query will refetch data whenever the window is refocused. This ensures that your app always has fresh data. You can disable this behavior if you don’t want automatic refetching.
const { data } = useQuery(
'userData',
fetchUserData,
{ refetchOnWindowFocus: false } // Disable auto refetch on focus
);
3. staleTime – Time Before Data Becomes Stale
React Query automatically keeps the data in memory and considers it “fresh” for a certain period (default is 0ms). After this period, the data becomes “stale,” and React Query will refetch it. You can modify the staleTime to control how long the data is considered fresh.
const { data } = useQuery(
'userData',
fetchUserData,
{ staleTime: 60000 } // Data stays fresh for 1 minute
);
4. cacheTime – Time to Keep Data in Cache
cacheTime controls how long React Query keeps unused data in its cache before garbage collecting it. This can be useful if you want to keep data around for a while after it’s no longer used.
const { data } = useQuery(
'userData',
fetchUserData,
{ cacheTime: 300000 } // Keep data in cache for 5 minutes
);
Handling Errors and Loading States
useQuery provides a set of built-in states to manage loading and error handling:
- isLoading: True while the query is fetching data for the first time.
- isError: True if there’s an error during the query.
- error: The error object, which can contain error messages or details.
- isSuccess: True when the query completes successfully.
Here’s an enhanced example to handle loading, error, and success states:
function UserProfile() {
const { data, error, isLoading, isError } = useQuery('userData', fetchUserData);
if (isLoading) return <div>Loading user data...</div>;
if (isError) return <div>Error: {error.message}</div>;
return (
<div>
<h1>User Profile</h1>
<p>Name: {data.name}</p>
<p>Email: {data.email}</p>
</div>
);
}
Fetching Data on Demand with refetch
Sometimes, you may want to fetch data on demand (e.g., when a user clicks a button). React Query provides a refetch method that can be called manually to trigger a re-fetch of the data.
Here’s an example:
function UserProfile() {
const { data, refetch, isLoading, isError } = useQuery('userData', fetchUserData, {
enabled: false, // Don't auto-fetch on mount
});
return (
<div>
<h1>User Profile</h1>
{isLoading && <p>Loading...</p>}
{isError && <p>Error: {error.message}</p>}
{data && (
<>
<p>Name: {data.name}</p>
<p>Email: {data.email}</p>
</>
)}
<button onClick={() => refetch()}>Fetch Data</button>
</div>
);
}
In this case, the data won’t be fetched automatically when the component mounts because enabled is set to false. The user can manually trigger the data fetch by clicking the Fetch Data button.
Conclusion
The useQuery hook is a game-changer for managing data fetching in React. It simplifies the entire process, automatically handling loading states, caching, error handling, and background refetching. With its powerful and customizable features, React Query helps developers focus on building user-friendly features rather than managing complex data-fetching logic.
Whether you’re building a small app or a large-scale production project, React Query should be a key part of your data-fetching strategy, making your app more efficient and easier to maintain.