React Query 101

React Query 101

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 paste 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:

PS: Miss you GraphQL :(