undo
Go Beyond the Code
arrow_forward_ios

React Query will make your life (and your app) easier

Luca Dardenne
Software Engineer & Solver
October 30, 2025
To learn more about this topic, click here.

Introduction

You’ve probably made an API call inside a `useEffect` hook, saved the response, and used that data in your component. It’s simple and works fine at first, but as your app requirements grow, you end up adding loading states, refetch logic, retries, error handling, and more. Before you realize it, you’ve built all these solutions yourself and turned your component into an unmaintainable source of bugs.

You may have heard of React Query but thought it wasn't worth the time to learn, or you may have used it in a project without realizing how to take advantage of it. If so, this article is a good read for you:


The Problem: The useEffect hook

As mentioned earlier, relying on `useEffect` and custom data-fetching logic can work for simple cases, but as your application grows, you’ll run into some common problems, such as:

You could build your custom hook to handle these issues, but as we've seen, this quickly leads to reinventing the wheel. Instead, let's look at how React Query solves these problems for you:

The Solution: React Query abstraction

React Query is an abstraction that provides a declarative way to handle data fetching, caching, and synchronization. You define "queries" that resolve promises (in our case, API calls, but you can give it any async function), and it handles the rest:

The result is a massive improvement in developer experience, fewer bugs, and code that is far more reliable and readable.


Add it to your project:

1. Install the library



            


2. Configure the QueryClient

You need to create a `QueryClient` instance and provide it to your application using the `QueryClientProvider`. This client manages the cache and allows you to define default configurations that will apply to all of your queries. But don’t worry if you need different behavior for a specific query, you can override these directly in the `useQuery` hook call that we'll see right after.



            


You might be wondering about the `staleTime` property. It determines how long data saved in the cache is considered "fresh" before React Query marks it as stale and refetches it the next time you access it. By default, all the data is "stale" right after it is resolved.


3. Defining and Using a Query

The `useQuery` hook is the primary way to fetch and manage server data in React Query. It abstracts away much of the boilerplate and complexity of manual data fetching, caching, and state management.

To use `useQuery`, you need to provide two essential parameters:

Both parameters are required: `queryKey` ensures your data is stored and retrieved correctly from the cache, while `queryFn` defines how to get the data in the first place. If you think of the cache as a filing cabinet, `queryKey` is the label on the folder, and `queryFn` is the process for filling that folder with up-to-date information.

Pro Tip: Start your `queryKey` arrays with a common identifier (like "posts") for related queries. This makes it easier to invalidate or refetch all associated data at once.



            


4. A bit more advanced usage: Paginated Queries

In this example, we include the page number in our `queryKey`. This means that when the page state changes, React Query creates a separate cache entry for each page (e.g., ["posts", 1], ["posts", 2]).



            


With each page's data cached separately, when users navigate back to a page they have already visited, the data is instantly available from the cache without any network load.

5. Accessing the cache manually (with type safety)

React Query provides a `useQueryClient` hook for doing various operations against the cache. For example, retrieving some value from it:



            


The `queryClient` will retrieve the value from the cache (without fetching it) by searching for the matching key. However, there's a problem: the returned data is typed as any, which means we lose all the type safety we worked for.


The solution: `queryOptions`


React Query's `queryOptions` helper allows you to define reusable query configurations and centralize your queries:



            


Now you can access cached data with complete type safety:



            


You can also use these query options directly in your components:



            


This approach gives you cache type safety and reusable query logic, which translates into better maintainability as your application grows.


6. Handling data updates and invalidation

When data is modified via a mutation (creating, updating, or deleting), the cached data becomes stale. React Query provides the useMutation hook and the `queryClient.invalidateQueries` method to handle this.

Invalidation tells React Query that specific queries are out of date and should be refetched. Invalidation works by the `queryKey` hierarchy, which means that by simply telling the client to invalidate ["posts"], it will reach every query that starts with it.



            

A typical scenario is managing the cache during user authentication. After a user logs out, it is critical to clear the cache to prevent data from one user being shown to another. This can be achieved with `queryClient.clear()`.


Best Practices and Common Patterns

1. Better Cache Keys

Structure your query keys hierarchically for practical invalidation (as seen in the paginated example):



            


2. Optimistic Updates

For better UX, update the UI immediately and rollback on failure:



            


3. Dependent Queries

Chain queries that depend on each other by making use of the enabled setting:



            


Deep Dive

If you want to learn more about React Query, I'd recommend checking out Why React Query? by ui.dev. This post explains React Query in more depth and has more practical tips and examples.

Also, follow Dominik Dorfmeister (@TkDodo), he's a React Query maintainer and is always sharing content and helping people out.

And of course, the docs.


Conclusion

React Query unlocks a whole new way of thinking about how to handle server state in our applications and gives us the tools to build faster, more reliable, and synchronized applications. It improves what's most important: user and developer experience without compromise.

If you feel overwhelmed by some of these concepts, don't worry: you can start simply by replacing some `useEffect` with a basic `useQuery` call, then gradually adopt mutations, optimistic updates, and advanced patterns as you get comfortable.

Once you get into the rhythm, it's hard to go back to manual data fetching.

Luca Dardenne
Software Engineer & Solver

Start Your Digital Journey Now!

Which capabilities are you interested in?
You may select more than one.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.