Design Converter
Education
Last updated on Mar 21, 2025
•6 mins read
Last updated on Mar 21, 2025
•6 mins read
Software Development Executive - I
Builds things that work. And if it doesn’t, he’ll fix it — with Neovim, of course.
Managing server state in React apps can get tricky, but React Query simplifies the process. It handles data fetching, caching, and updates, so you don’t have to manage everything manually.
This blog breaks down how to update the React Query cache using key concepts like query keys, mutation responses, and cached data. You’ll also learn about advanced techniques, including cache management, handling multiple mutations, and using optimistic updates.
React Query leverages a query cache mechanism to store data fetched from the server and uses a query key to uniquely identify each data entry. Developers can create a custom hook that encapsulates this logic, ensuring that different components across a page can interact with the same cached data without unnecessary refetches.
Hook | Responsibility | Example Usage |
---|---|---|
useQuery | Fetches and caches data using a unique query key | useQuery('todos', fetchTodos) |
useMutation | Manages the mutation response and mutation state | const mutation = useMutation(updateTodo) |
Note: The empty array is often used as a default value for the cache if no data is available.
1import React from 'react'; 2import { useQuery } from 'react-query'; 3 4function Todos() { 5 // The query key 'todos' uniquely identifies this cached query 6 const { data = [], error, isLoading } = useQuery('todos', async () => { 7 // Simulate an async request to a database 8 const response = await fetch('/api/todos'); 9 return response.json(); 10 }); 11 12 if (isLoading) return <div>Loading...</div>; 13 if (error) return <div>Error loading todos.</div>; 14 15 return ( 16 <ul> 17 {data.map((todo) => ( 18 <li key={todo.id}>{todo.title}</li> 19 ))} 20 </ul> 21 ); 22}
In this example, React Query automatically handles the query cache with the query key 'todos', ensuring that cached data is available to any component that needs it. Developers should assume that if data is not present, the value defaults to an empty array.
React Query simplifies the process of updating server state by offering powerful tools like useMutation . This hook not only handles mutation responses but also supports optimistic updates to instantly reflect changes on the screen.
When performing a mutation, React Query allows you to modify the cache directly before receiving the final response from the server. This is achieved using callbacks like onMutate and the onSuccess callback (onSuccess) as the last argument in the configuration object.
onMutate: Modify the cache immediately.
onError: Roll back the cached data if the mutation fails.
onSuccess: Execute a callback when the mutation response is successful.
onSettled: Trigger query invalidation to refresh the server state.
1import React from 'react'; 2import { useMutation, useQueryClient } from 'react-query'; 3 4function UpdateTodo({ newTodo }) { 5 const queryClient = useQueryClient(); 6 7 const mutation = useMutation(updateTodo, { 8 // onMutate is called before the mutation function executes. 9 onMutate: async (newTodo) => { 10 // Cancel any outgoing refetches (so they don't overwrite our optimistic update) 11 await queryClient.cancelQueries('todos'); 12 const previousTodos = queryClient.getQueryData('todos') || []; 13 14 // Modify the cache directly by appending the newTodo to the cached data array. 15 queryClient.setQueryData('todos', (old) => [...old, newTodo]); 16 console.log('Optimistic update applied', newTodo); 17 // Return context with the previous value 18 return { previousTodos }; 19 }, 20 onError: (err, newTodo, context) => { 21 // Roll back the cache if mutation fails. 22 queryClient.setQueryData('todos', context.previousTodos); 23 console.error('Mutation failed, rolled back changes'); 24 }, 25 // The onSuccess callback ensures any post-mutation response logic is executed. 26 onSuccess: (data) => { 27 console.log('Mutation response received:', data); 28 }, 29 // onSettled invalidates the query to ensure the cached data is in sync with the server state. 30 onSettled: () => { 31 queryClient.invalidateQueries('todos'); 32 }, 33 }); 34 35 // Return useMutation instance for further use 36 return ( 37 <div> 38 <form 39 onSubmit={(e) => { 40 e.preventDefault(); 41 // Use the mutate method to trigger the mutation 42 mutation.mutate(newTodo); 43 }} 44 > 45 <input type="text" name="title" placeholder="Todo title" required /> 46 <button type="submit">Submit</button> 47 </form> 48 </div> 49 ); 50}
In this example, the developer demonstrates a complete use case where a component submits a post to update a database. The mutation function uses several callbacks:
• onMutate to update the query cache immediately,
• onError to revert changes if an error occurs,
• onSuccess as a callback to log the mutation response, and
• onSettled to perform query invalidation.
This logic ensures that cached data is updated optimistically, giving the user immediate feedback while maintaining the integrity of the server state.
The React Query cache (or QueryCache) is a core part of React Query's design. It holds the cached data, meta information, and the status of each query. Advanced techniques focus on updating this cache immutably and efficiently without causing unnecessary refetches.
Using methods like setQueryData requires that the cached data be updated in an immutable manner. This ensures that React Query can properly detect changes and update the component views accordingly. Here’s an example:
1// Updating the cache in one line while ensuring immutability 2queryClient.setQueryData('todos', (oldData = []) => 3 oldData.map((todo) => 4 todo.id === updatedTodo.id ? { ...todo, ...updatedTodo } : todo 5 ) 6);
Note: Always log the context when performing such operations to verify that variables and arguments are processed correctly.
This diagram outlines the logic flow where:
• A user action triggers the mutation,
• The cache is updated optimistically (using multiple mutations if needed),
• The server returns a mutation response,
• The React Query cache is updated immutably, and
• Query invalidation is performed to sync with the database.
To build a smooth and responsive application, developers should focus on:
• Efficient state management using React Query with clear query keys.
• Leveraging optimistic updates and mutation responses to update the cache directly.
• Implementing custom hooks that import and encapsulate React Query logic for different components.
• Using callbacks like onMutate, onSuccess, and onSettled as the last argument to handle mutation events.
• Ensuring cached data is updated immutably to avoid bugs, particularly when dealing with an empty array or undefined values.
• Understanding the use case of handling async data fetches and post requests that modify the server state.
By integrating these strategies, you can create components that not only fetch and display data effectively on the screen but also log important details, manage error states, and maintain a consistent client state across the page. This approach keeps your application efficient, user-friendly, and robust.
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.