I've always looked at react-query
from far away, before I was mainly working with GraphQL and Apollo was a great solution for my querying needs.
Fast forward a couple years and now I am at a different job and I am not using GraphQL anymore. Having heard a lot about react-query I decided to give it a try and see how good it really is.
And...
Insert drum roll
It's actually pretty great, it has all the goodies I enjoyed in Apollo and it's not restricted to GraphQL.
Here is a quick guide to demonstrate some of the many benefits/features of react-query.
First let's create a react project, create-react-app is perfect for this simple demo app.
npx create-react-app react-query --template typescript
# or
yarn create react-app react-query --template typescript
Yes, I am adding TypeScript to this one page application, I can't deal with those yellow icons anymore
Now navigate inside the react-query
folder that was just created.
If you are in the terminal just do
cd react-query
Now let's install react-query
npm i react-query
# or
yarn add react-query
Let's also install axios
to use it instead of fetch
npm i axios
# or
yarn add axios
Now inside src/App.tsx
past the following code
import React from 'react';
import './App.css';
function App() {
return (
<div className="App">
<h1>Random Food Generator</h1>
<button>Generate</button>
<img src="insert food url here"
alt="food"/>
</div>
);
}
export default App;
So the promise of this app is pretty simple, we press a button to get a randomly generated dish. In order to do this we will use the food api ,react-query and axios.
First we need to wrap our in app inside a <QueryProvider />
to connect the queryClient
.
Inside src/index.tsx
let's import QueryClient
and create a new client for the app.
// ...other code
import { QueryClient, QueryClientProvider } from "react-query";
// ...other imports
const queryClient = new QueryClient();
And let's use the QueryClientPovider
with the new client we just created.
Still inside src/index.tsx
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>,
document.getElementById("root")
);
And that's all we need to start firing our queries!
The magic of useQuery
Hooks are here to stay and most of my favorite libraries are using them, react-query
is no exception. The useQuery
hook is pretty cool, we give it a unique key and a function that returns a promise. In exchange we get the data and other useful props.
Let's see in action, inside src/App.tsx
let's add the following code.
First let's import useQuery
and axios
.
import { useQuery } from "react-query";
import axios from "axios";
Now inside the App
component let's use useQuery
const { data } = useQuery("food", () =>
axios.get("https://foodish-api.herokuapp.com/api/")
);
and now in the JSX
return (
<div className="App">
<h1>Random Food Generator</h1>
<button>Generate</button>
<img src={data?.image} alt="food" />
</div>
);
So far so good, everything seems to work, but if you pay attention you may notice some strange behavior. For example if you go to another tab in your browser, when you come back the query is re-fetched. This is one of the things that caught me off guard when trying react-query
for the first time, I remember not having a clue of what was going on and just switching to something else.
Well, apparently it's important to read the docs. react-query
has some defaults that can be aggressive but you can easily change them to what you need or you are used to.
These are my defaults.
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 3600,
refetchOnWindowFocus: false,
},
},
});
So now we are not re-fetching on window focus and we actually have a stale time.
Besides data
we have access to other props that can help us build a better UI by telling us the state of the query.
Let's take a look.
const { data, isFetching, isError, refetch } = useQuery("food", () =>
axios.get("https://foodish-api.herokuapp.com/api/")
);
There are more, but we will use these for now. I think these props are pretty self explanatory, let's use them to let the user know what's going on with the query.
function App() {
const { data, isFetching, isError, refetch } = useQuery("food", () =>
axios.get("https://foodish-api.herokuapp.com/api/")
);
if (isError) {
return <p>Oops an error happened</p>;
}
return (
<div className="App">
<h1>Random Food Generator</h1>
<button type="button" onClick={() => refetch()}>
Generate
</button>
{isFetching ? (
<p>Loading...</p>
) : (
<img src={data?.data?.image} alt="food" />
)}
</div>
);
}
So first we check for any errors, then connect the button to the refetch function and finally display a loading state when the image is being fetched.
Can we do all of these with fetch
?
Yeah of course, but it would have taken a lot more code. A useEffect
for fetching the initial data, creating state for the loading and error, and putting everything into a function to do the re-fetch.
This is only scratching the surface of what react-query
can do, we didn't even look into the cache how it can replace state management tools like redux.
Anyways, I hope this got you interested in checking react-query
, because there is so much more that you can do.
Over and out.
Code: https://github.com/ivanms1/react-query-101
PS: Miss you GraphQL :(
Top comments (3)
@tannerlinsley @ivanms1 Thanks for this Post, but 2 more questions?
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
is now mandatory in v3 ? This is more code to do/maintain!
Try using GraphQL-Request or any other client with React Query ;)
react-query.tanstack.com/graphql
Will do!
Thanks for all you do Tanner, big fan of your work.