React Query - The Definitive Guide to Making Requests in React

You might have been fetching data in your React Apps WRONG ❌

React itself does not have any opinions about how we fetch data from the Server.

react meme

The most commonly used are FetchAPI and the Axios library.

Wrong Approach 1 : Fetch API 🎣

This is the most basic approach to use the Browser's Fetch API.

It is called when a component first mounts, using the useEffect hook and manage the response with useState. But, this is not the recommended way for good developers(not even the best ones).

Fetch is not a good option because of :

  • A lot more code is required for simple tasks.
  • Lack of any useful functionality that makes the developer experience very bad.

React with Fetch Sample

Wrong Approach 2 : Axios πŸͺ“

The Axios library simplifies the above code a bit, but still lacks some features for a great developer experience.

It abstracts the code for fetching, but does not help in :

  • Caching.
  • Data states like Error or loading.
  • Still has try-catch hell.

React with Axios Sample

Now that the above 2 magical ways are wrong, what is the correct way?

React Query - A Library that simplifies the way we fetch, cache and synchronize data from a server

Tanstack Query

Yes, both of the above are the same thing.

Let's see what React Query or Tanstack Query does and is in detail.

It is not Just A Data Fetching Library

What React Query gives you is not just the ability to describe which data you want and to fetch that data, it also comes with a cache 🧠 for that server data.

This means that you can just use the same useQuery hook in multiple components, and it will only fetch data once and then subsequently return it from the cache.
React Query

Enough with the theory, show me the Code ⌨

Now, let's see how to set it up and some use cases where React Query just feels awesome.

Setup (The Boring Part)

First of all you need to have a project setup, either in ReactJS or in NextJS.
I will just use React Query in my LynkitπŸ”— project.

To add React Query to the project, we use the following command :

npm i @tanstack/react-query
Enter fullscreen mode Exit fullscreen mode

Now if you have used something like Redux or ContextAPI before, you have to make the client available to all the parts that you will be using.

The simplest way to do this is by wrapping the parts of your application inside a client-provider, which in this case is called the QueryClientProvider.

We also need to create a client which handles all the functionality of the Library.

[!Important]
Client is nothing but an instance of the base class of React Query in this case. It handles all the functionalities that we want to use.

For my project(an in most cases), I want to access the QueryClient from all scopes of the App, so we wrap the whole App component inside it.

import {
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'

const queryClient = new QueryClient()

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </QueryClientProvider>
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

Now that React Query is Setup, it is time to look at it's various use cases.

Fetching Data

When making a Get Request, we use the useQuery hook provided by the React Query library.

Note : If hooks are something you don't understand, I want you to stop here and look at this : Hooks : JS in plain english by Ayush Verma.

Okay, so useQuery hook.
Queries are basically asynchronous requests tied to a data source. And async requests are handle by promises in Javascript.

So, the useQuery hook are used to subscribe to a query that listens to the pending, fulfilled and rejected states and returns a state based on that.

A lot of jargon? Yeah, agreed. So here's a diagram :

Use Query Subscription

Now the hooks take in 2 parameters mainly :

  • A unique key πŸ”‘ for the query
  • A function that returns a promise that:
    • Resolves the data βœ… , or
    • Throws an error ❌

πŸ“’ The unique key you provide is used internally for refetching, caching, and sharing your queries throughout your application.

Let's see an example from Lynkit where I fetch the logged in user data :

The Actual Data Fetching using Axios.get()

Yes, for fetching data we have to use Axios but not directly.
We define a function to get the data and add it as a Query Function to the User key.

So, whenever I want to use the User data, I can just use the UseQuery hook to get the user data :

  • If found in the Query cache, it returns the data from there.
  • Other it calls the following function :
export const getUserData = async() : Promise<ProfileData> => {
    try {
      const response : AxiosResponse = await axios.get("http://localhost:3003/user", {
        headers : {
          "Content-Type" : "application/json",
        },
        withCredentials: true,
      });
      const {status, data } = response;
      console.log(data)
      if(status!==200 || !data.user){
        ...
        return nullUser;
      }
      return data.user as ProfileData;

    } catch (error) {
      console.log(error);
      throw new Error("Data Fetching Failed")
    }
}
Enter fullscreen mode Exit fullscreen mode

The UseQuery hook is used as follows :

const {data, isError, isLoading} = useQuery<ProfileData>(
    {
      queryKey : ["user"],
      queryFn : getUserData,
      notifyOnChangeProps : ['data','error'],
      refetchOnWindowFocus: false
    }
  )
Enter fullscreen mode Exit fullscreen mode

The above works as follows :
Step 1 : Read the Query Key passed, in this case : ["user"]
Step 2 : Check if query key is present in the Query Cache and return if present.
Step 3 : If the data is not present in the cache, called the function passed in the queryFn property.

πŸ“Œ Note that useQuery hook is used only for get ↩ requests. For other requests like, we use the useMutation hook with the mutate method.

Invalidating Queries β—πŸ“›

There will be cases, where you want to re-fetch data when an action occurs.
React Query also provides a way for us to do that : client.invalidateQueries() method.

You need to pass in the same queryKey that you want to be re-fetched and the QueryClient auto fetches the data when that data is invalidated.

const queryClient = useQueryClient();
queryClient.invalidateQueries(["user"]);
Enter fullscreen mode Exit fullscreen mode

The cached user data is removed and the new data from API or server is fetched again at this point.

The above can be used in 2 conditions :

  • The data is updated by adding, editing or deleting some part(like updating user name or password or image).
  • Logging out ❌ the user.

At this point you are ready to develop a sophisticated query based application.

For more detailed and complete explanations, you can check out @tkdodo πŸ‘ˆ profile and the Complete Tanstack Docs πŸ’‘.

You can also checkout my previous blog on Bun - Fastest Javascript Runtime ⚑