react-query v2 was released recently, which once again brought about a change in the react-query API along with overall performance improvements. It’s pegged at version 2.5.4 at the time of writing this post, and a comprehensive list of the updates can be found on the changelog.
In this post, we’ll discuss briefly the new changes and rewrite an app built with react-query v1 in a previous article to use the new version.
Moving on, I’ll be discussing these changes in the following sections, but it is essential that you check this article where I talked about react-query and built a sample first.
The idle
query state
In the older versions of react-query, dependent queries were expected to start in the success
state. A new query state has been introduced and can be used as the default state for dependent queries. As a result, appropriate messages can be displayed based on the state of the query. An example from the changelog is:
const { status } = useQuery(queryKey, queryFunction) return status == 'idle' ? 'Not ready' : status == 'loading' ? 'Loading data' : status === 'error' ? error.message : 'The data'
queryCache.invalidateQueries(queryKey)
Formerly queryCache.refetchQueries(queryKey)
, the new API method re-fetches only active queries, unlike in the previous versions, where all matching queries were re-fetched regardless of their state.
The new global configuration object
The new global configuration object is now sectioned. Sectioning here means that react-query objects from a family are grouped under one sub-object. The new global configuration has three parts:
- The
shared
section, housing the suspense option. - The
queries
object, housing all query-related options. - The
mutations
object, housing all mutation-related options.
The new global object looks like this:
const globalConfig = { shared: { suspense, }, queries: { ...queries }, mutations: { ...mutations }, }
In the course of rewriting the application from the previous version, we’ll restructure our app’s global object to follow the new pattern.
New query status Booleans
From version 2, a query can be assigned to a single variable without having to split them into separate variables. The new query statuses added are:
isLoading, isSuccess, and isError
Here’s an example:
Before:
const { data, isFetching } = useQuery("Recipes", fetchRecipes); { isFetching ? "Fetching" : data }
After:
const recipeQuery = useQuery("Recipes", fetchRecipes); return recipeQuery.isLoading ? ("Loading recipes") : recipeQuery.isError ? ( recipeQuery.error.message ) : ( <div> recipeQuery.data )
These are some of the highlights of the v2 release. Other updates, mostly bug fixes and minor updates, can be reviewed in the changelog.
Migrating from v1 to v2
In this section, we will be migrating a previously built app from react-query v1 to v2. I assume that you have experience writing JavaScript and React, and using Yarn and Git.
We’ll start off by cloning the GitHub repository and updating react-query:
git clone https://github.com/Youngestdev/react-query-app
After cloning:
cd react-query-app & yarn install & yarn upgrade react-query
In our application, only three components were affected by the new changes. The recipe, recipes and main component, we’ll be reflecting the new changes in them. We’ll start with the main component:
src/index.js
In this component, we’ll reconfigure the global configuration object. Replace the existing code with the block below:
import React, {lazy} from "react"; import ReactDOM from "react-dom"; import {ReactQueryConfigProvider} from "react-query"; import {ReactQueryDevtools} from "react-query-devtools"; const Recipes = lazy(() => import("./components/Recipes")); const Recipe = lazy(() => import("./components/Recipe")); const queryConfig = { shared: { suspense: true }, queries: { refetchOnWindowFocus: true } }; function App() { const [activeRecipe, setActiveRecipe] = React.useState(null); return ( <React.Fragment> <h1>Fast Recipes</h1> <hr/> <ReactQueryConfigProvider config={queryConfig}> <React.Suspense fallback={<h1> Loading ...</h1>}> {activeRecipe ? ( <Recipe activeRecipe={activeRecipe} setActiveRecipe={setActiveRecipe} /> ) : ( <Recipes setActiveRecipe={setActiveRecipe}/> )} </React.Suspense> </ReactQueryConfigProvider> <ReactQueryDevtools initailIsOpen={false}/> </React.Fragment> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App/>, rootElement)
The global configuration has been modified as highlighted above. We also added a new option, refetchOnWindowFocus
, that re-fetches the rendered query when the browser window is visited.
Next, we’ll update the Recipe
component.
src/components/Recipe.jsx
In this component, we’ll update the query variables to use the new query status Boolean. Replace the existing code with this:
import React from "react"; import {useQuery} from 'react-query'; import Button from "./Button"; import {fetchRecipe} from "../queries"; export default function Recipe({activeRecipe, setActiveRecipe}) { const recipeQuery = useQuery( ["Recipe", {id: activeRecipe}], fetchRecipe ); return ( <React.Fragment> <Button onClick={() => setActiveRecipe(null)}>Back</Button> <h2> ID: {activeRecipe} {recipeQuery.isFetching ? "Loading Recipe" : null} </h2> {recipeQuery.data ? ( <div> <p>Title: {recipeQuery.data.title}</p> <p>Content: {recipeQuery.data.content}</p> </div> ) : null} <br/> <br/> </React.Fragment> ); }
The bolded code indicates the updated parts of the code.
Next, we update the Recipes
component.
src/components/Recipes.jsx
In this component, we’ll update the query handler and render style. We’ll also replace the use of refetchQueries
to invalidateQueries
.
Replace the existing code with this:
import React from "react"; import {queryCache, useQuery} from "react-query"; import Button from "./Button"; import {fetchRecipe, fetchRecipes} from "../queries"; export default function Recipes({setActiveRecipe}) { const recipesQuery = useQuery("Recipes", fetchRecipes); return ( <div> <h2>Recipes List <br/> {recipesQuery.isFetching ? "Loading" : <Button onClick={() => { queryCache.invalidateQueries("Recipes") }}> Refresh Recipes </Button> } </h2> {recipesQuery.data.map(Recipe => ( <p key={Recipe.title}> {Recipe.title} <Button onClick={() => { // Prefetch the Recipe query queryCache.prefetchQuery(["Recipe", {id: Recipe.id}], fetchRecipe); setActiveRecipe(Recipe.id); }} > Load Recipe </Button>{" "} </p> ))} </div> ); }
We have successfully migrated our app from v1 to v2. Our app functions the same way as it was before.
Conclusion
The new updates to react-query are excellent. The addition of new query status booleans makes it easy to display appropriate messages at every query status.
Check here to reference the code snippets used in the new features explanations above. Keep building amazing things, and be sure to keep checking the blog for crispy new posts ❤ .
LogRocket: Full visibility into your production React apps
Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket combines session replay, product analytics, and error tracking – empowering software teams to create the ideal web and mobile product experience. What does that mean for you?
Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay problems as if they happened in your own browser to quickly understand what went wrong.
No more noisy alerting. Smart error tracking lets you triage and categorize issues, then learns from this. Get notified of impactful user issues, not false positives. Less alerts, way more useful signal.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps — start monitoring for free.