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.
idle
query stateIn 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 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:
shared
section, housing the suspense option.queries
object, housing all query-related options.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.
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.
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.
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 ❤ .
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]