Modern web applications actively work with data: they receive it from servers, update, cache and synchronize. However, managing the state of asynchronous data in React applications can be a difficult task, especially when taking into account caching, requeries and error handling.
React Query is a powerful library created to simplify the work with server data in React. This React Query Tutorial from Celadonsoft helps developers manage asynchronous requests, improves application performance and makes the code more predictable.
What Problems Does React Query Solve?
Before getting into the details of working with the library, it is important to understand what difficulties developers from any MVP development company like Celadonsoft face when managing data in React applications:
- Manual state management: The standard approach is to manually monitor boot status, errors, data updates and caching. This increases the complexity of the code and the likelihood of errors.
- Excessive server requests: Without caching, data can be reloaded for each render, which causes server overload and reduces application performance.
- Data inconsistency: If you have multiple components that use the same server data, you may experience a synchronization issue: one component updates the data while others continue to use outdated information.
- No built-in automatic update mechanisms: The developer has to manually track changes and implement repeated requests for data updates.

Basic Concepts of React Query
Working with asynchronous data in React applications often leads to code complexity: you have to manually manage the state of boot, caching and updating of data. React Query solves these problems by providing a convenient API and automatic data processing mechanisms. Let’s look at the key concepts on which React Query is built.
Data Caching: How and Why?
Caching is one of the key features of React Query, allowing:
- Reduce the load on the server by reusing previously downloaded data.
- Speed up rendering, avoiding unnecessary requests.
- Automatically update cache so that users always see the most relevant information.
React Query uses the Query Cache, which stores the data of queries and manages their obsolescence. Cache settings allow you to control the data lifetime, update it in background and prevent repeated requests.
Handling Boot States and Errors
The standard approach to working with fetch or axios is to manually monitor boot status, errors and successful data retrieval. React Query simplifies this process:
- isLoading – boot state.
- isError is an error flag that accompanies error.message.
- isSuccess – Indicates the successful execution of a query.
These states can be used to improve the user experience, for example by showing the spin during boot or displaying fallback content when errors occur.
Automatic Update and Re-Request of Data
One of the most useful functions in our React Query Tutorial is automatic data synchronization. The library allows:
- Automatically repeat requests for errors using the retry mechanism.
- Update data in the background when the user returns to the page (refetchOnWindowFocus).
- Resubmit data when interacting, for example when a component is re-assembled.
These mechanisms eliminate the need for manual data updates, improving performance and ease of use with the API.

Setting Up and Configuring React Query
React Query is a powerful tool for managing the state of the server in React applications. But before using its advantages, one has to install and configure the library correctly. In this section, we will go over the main steps of this process.
1. Installation of Libraries and Dependencies
React Query easily integrates into any existing project. Installation is done via npm or yarn.
This package includes handy tools for debugging requests in the browser.
2. Create and Configure Queryclient
Before you start working with React Query, you should create QueryClient-an object responsible for managing data caching and request settings. Such an object gives you the opportunity to manage the strategy of caching and updating data in the application.
3. Wrapping the Application to Queryclientprovider
For React Query to work with queries, you need to provide the created queryClient to the entire application using QueryClientProvider. You do this in the main application file (typically App.tsx or index.tsx).
Now, all components within <App /> can use React Query to work with data.
4. (Optional) DevTools Connection for Debugging
To make debugging requests easier, React Query provides React Query DevTools. They let you see the status of your cache, what requests are running, and what their status is in real time.
By default, the debug panel is closed, but you can open it in the developer interface.
Fetching Data with useQuery
One of the most critical activities when developing modern React applications is working with server data. React Query facilitates very comfortable and powerful useQuery hooks for simplification of data retrieving, state managing, and caching. Below, we will discuss how to work with useQuery in real projects.
1. Basics of Using useQuery
useQuery is the main tool for running GET-queries in React Query. It allows:
- Automatically handle statuses like fetching, success, and error.
- Keeps cache of data and refetches if it’s needed
- Share the result between multiple queries
2. Understanding the useQuery Parameters
React Query has flexible settings for useQuery and allows adjusting its behavior depending on the situation. For example, look at the main parameters:
- queryKey is a unique query key used for caching and disabling query
- queryFn is a function that performs API request.
- enabled: Allows you to manage automatic execution of the request. Useful if the request is only to be executed under certain conditions.
- staleTime: Defines how long cached data is considered current (reduces the number of requests to the server).
- refetchOnWindowFocus: Updates the data at browser focus.
3. Processing Different Query States
useQuery returns an object containing useful states that help manage the UI:
- isLoading: Data load (first request).
- isFetching – Background data update.
- error is a query error.
- data – The data obtained.
4. Optimizing useQuery
Some strategies for optimizing performance and improving user experience are as follows:
- Setting staleTime – decreases the number of duplicate requests.
- Use initialData – allows the display of old data while new ones are loaded.
- Grouping queries with queryKey – prevents running duplicate queries.

Data Mutations with useMutation
Unlike useQuery, which is used to retrieve data, useMutation in React Query is intended for modifying the data – creating, updating or deleting records on a server. This section will cover the key aspects of working with mutations and how to use useMutation effectively in your applications.
What Are Mutations and When to Use Them?
Mutations are operations that change data on the server. They can be used in the following scenarios:
- Sending a form with new data (for example, creating a new user).
- Update existing data (for example, change of user profile).
- Delete entries (for example, deleting a comment or post).
Because mutations change state, their processing differs from read requests (useQuery). They are not cached and require an explicit call to execute.
Using useMutation: A Basic Example
To work with mutations, you need to call useMutation and pass a function to it that requests the server. mutation.mutate() is used to execute the query, and mutation.isLoading, mutation.isError and mutation.isSuccess help manage the query states.
Optimistic Updates: Improved Responsiveness of the UI
Upbeat updates allow the UI to be updated before receiving a response from the server, creating an instant feedback experience. React Query supports this mechanism via onMutate and onError.
- onMutate – Updates the UI before requesting a server.
- onError – Returns changes when an error occurs.
- onSettled – updates the data after the mutation is complete.
This approach makes the interface more fluid and responsive.
Disabled Queries: Automatic Data Update
After successful mutation, it is important to update the old data so that the user can see the current information. You can use queryClient.invalidateQueries(). This call will force the cached data to match the data on the server.
Cache Management and Data Update
Efficient cache management is one of the key features of React Query. It helps to minimize the number of network requests and increases the productivity of the application. Let’s take a look at the basic cache management techniques.
Disabled queries: when and how to use
Disabled allows to force update data in the cache by requesting it again from the server. This is useful when:
- The data could be changed on the server after mutation;
- the user has performed an action requiring data update (for example, adding a comment);
- You need to delete outdated information.
Prefetching
Prefetching allows you to load data before the user requests it, reducing the delay in rendering.
Setting the background refresh and refresh time
To avoid frequent requeries, staleTime and refetchInterval can be configured.

Advanced React Query Features
Parallel and Dependent Queries
When multiple queries can be executed simultaneously, React Query runs them in parallel:
const { data: users } = useQuery(['users'], fetchUsers);
const { data: posts } = useQuery(['posts'], fetchPosts);
But if one request depends on another, you can use enabled:
const { data: user } = useQuery(['user', userId], () => fetchUser(userId));
const { data: orders } = useQuery(
['orders', user?. id],
() = > fetchOrders(user.id),
{ enabled: !! user } // Request is only executed after receiving the user
);
Pagination and Infinite Scroll
For loading data in batches, React Query offers useInfiniteQuery:
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
['products'] is a
({ pageParam = 1 }) => fetchProducts(pageParam),
{
getNextPageParam: (lastPage, pages) => lastPage.nextPage ?? undefined,
}
);
This approach is useful for newsfeeds, product lists and other dynamically loaded data.
Real Time Data Update
React Query does not support WebSocket directly, but you can combine it with refetchInterval or use onMessage within a WebSocket connection to update the data.
Example with WebSocket:
const socket = new WebSocket('wss://example.com');
socket.onmessage = (event) => {
queryClient.setQueryData(['notifications'], (oldData) => [...oldData, event.data]);
};
This allows the interface to be updated instantly when new data is received.

Testing Components Using React Query
When developing according to the React Query Tutorial, it is important to consider not only the performance and optimization of data handling, but also the correctness of the code. Good testing of queries and mutations allows avoiding unexpected bugs and ensure the stability of the application.
Data Mining and Query Testing
Testing useQuery requires isolation of the request from the real API, which can be achieved by using a buffer:
- Use MSW (Mock Service Worker) to switch network requests. This helps to test the behavior of applications in different scenarios without dependency on backend.
- Use the Jest + React Testing Library to render components and check their performance in response to different query states (isLoading, isError, isSuccess).
- Replace fetch or Axios with Mocks using jest.fn() or vi.fn() (for Vitest).
Testing Mutations and Optimistic Updates
When testing useMutation it is important to consider not only successful scenarios, but also possible errors:
- Checking the correctness of the mutation call when sending data.
- Testing of UI error handling and behavior in case of failure.
- Simulation of optimistic updates to ensure that when the status is rolled back, the data returns correctly to the previous value.
- Use waitFor() from the React Testing Library to asynchronously wait for a mutation completion before checking the final state of the component.
Integration Testing
In addition to the modular testing of individual components, it is important to check the system’s performance as a whole:
- Run integration tests from a mock-server or in-memory database.
- Checking the interaction of multiple components using React Query.
- Testing the behavior of the application when the network state changes (offline-first approach).
The good approach to testing React Query helps avoid unexpected bugs and makes the code more reliable.
Conclusion
React Query Tutorial significantly simplifies the work with server data in React applications. It provides an opportunity to decrease routine code, increase the performance of caching and its auto-updating for developers from the company Celadonsoft, and improve user experiences.