DEV Community

Cover image for Getting Started with React Query
Nischal Dutt
Nischal Dutt

Posted on • Edited on

Getting Started with React Query

The Missing Data Fetching Library

React-Query is often described as “The missing data fetching library” for React, because of the reason that React is a UI library that has no opinions on how we fetch the data from the server. The most basic form used by front-end developers to fetch the data is using Javascript’s fetch API, handling the requests with useEffect hook and managing the components states like loading, error, and resulting data using useState hook. Initially, the world is all fairyland but as soon as the project starts picking up stacks of complexity and we might require features like caching, synchronizing, and updating the server state, that’s when the reality hits hard and developers are left with the following options:

- Build your way to fetch and manage data: Since React applications do not come with an opinionated way of fetching or updating data from your components so developers end up building their ways of fetching data. This usually means cobbling together component-based states and effects using React hooks and keeping on building as things are. Though this is not a scalable approach and results in issues for which we are finding solutions in the first place.

- Using react management libraries: Another way is to use the state management libraries for React applications like Redux, Mobix, etc which store and provide asynchronous data throughout the app. While most traditional state management libraries are great for working with client states, they are not so great at working with async or server states. This is because the server state is completely different from the client state and remote as well.

According to React-Query docs, the server state:

  • Is persisted remotely in a location you do not control or own
  • Requires asynchronous APIs for fetching and updating
  • Implies shared ownership and can be changed by other people without your knowledge
  • Can potentially become "out of date" in your applications if you're not careful

Managing the server state with these libraries becomes even more challenging when the developers have to deal with caching, deduping multiple requests for the same data, updating stale data in the background, and performance optimizations like pagination and lazy loading data, etc.

The developers have to tackle all or most of these challenges and believe me we’re only scratching the surface here!

This is the hole that React-Query tries to fill up. It is one of the best if not the best, libraries for managing server state that works with zero-config and can be customized as per application needs.

According to React-Query docs, some advantages are as follows:

  • Reduction in Lines Of Code of your application
  • More emphasis on building new features instead of worrying about wiring up new server state data sources.
  • End-user satisfaction as data fetching is optimized and your application feels faster and more responsive.
  • Potentially help you save on bandwidth and increase memory performance

Installation

In your react app, You can install React Query via NPM

$ npm i @tanstack/react-query
# or
$ yarn add @tanstack/react-query
Enter fullscreen mode Exit fullscreen mode

React query library module exports a provider component QueryClientProvider inside which we can wrap our App component.

import React from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

const App = () => {
  return (
    <QueryClientProvider client={queryClient}>
       {/* The rest of your application */}

      <ReactQueryDevtools initialIsOpen={false} position="bottom-right" />
    </QueryClientProvider>
  );
};
Enter fullscreen mode Exit fullscreen mode

Wrapping our app with the provider enables our app to leverage all the features that React-Query provides. We also need to instantiate the query client and pass it as a prop to the provider component. Now every child component wrapped inside the provider can fetch data using react query!
Also, React-Query provides out-of-the-box dedicated Devtools to facilitate debugging during the development. It helps in visualizing all of the inner workings of React Query and will likely save you hours of debugging if you find yourself in a pinch!

The devtools are a separate package that you need to install:

$ npm i @tanstack/react-query-devtools
# or
$ yarn add @tanstack/react-query-devtools
Enter fullscreen mode Exit fullscreen mode

By default, React Query Devtools are only included in bundles when process.env.NODE_ENV === 'development', so you don't need to worry about excluding them during a production build.

Setting up async mock API

To emulate the data fetching process We are going to use the JSON-Server to create a /companies route that returns a list of companies.
Using a standard approach to fetch the data and display it on the web page we use React’s useEffect and useState hooks as follows:

import React, { useEffect } from "react";

async function fetchCompanies() {
  return await fetch(`http://localhost:4000/companies`).then((response) =>
    response.json()
  );
}

const Companies = () => {
  const [companies, setCompanies] = React.useState([]);
  const [isLoading, setIsLoading] = React.useState(true);
  const [isError, setIsError] = React.useState(false);

  useEffect(() => {
    fetchCompanies()
      .then((response) => {
        setCompanies(response);
        setIsLoading(false);
      })
      .catch((_error) => setIsError(true));
  }, []);

  if (isLoading) {
    return <div>loading</div>;
  }

  if (isError) {
    return <div>error occurred</div>;
  }

  return (
    <div>
      {companies.map((company) => {
        return <div key={company.id}>{company.name}</div>;
      })}
    </div>
  );
};

export default Companies;
Enter fullscreen mode Exit fullscreen mode

It is evident from above that even for such a simple scenario we have to maintain 3 state variables to handle the application state.

Now let's try this with React-Query now

import React from "react";
import { useQuery } from "@tanstack/react-query";

async function fetchCompanies() {
  return await fetch(`http://localhost:4000/companies`).then((response) =>
    response.json()
  );
}

const Companies = () => {
  const {
    isLoading,
    data: companies,
    isError,
    error,
  } = useQuery(["companies"], fetchCompanies);

  if (isLoading) {
    return <div>loading</div>;
  }

  if (isError) {
    return <div>{error.message}</div>;
  }

  return (
    <div>
      {companies.map((company) => {
        return <h1 key={company.id}>{company.name}</h1>;
      })}
    </div>
  );
};

export default Companies;
Enter fullscreen mode Exit fullscreen mode

Woah! What just happened? What’s this useQuery and what is it returning? Why aren’t we maintaining loading and error states separately?

Let us understand one thing at a time.

useQuery is the hook made available by React-Query that is used to fetch the data from external API resource. By definition, it can have 3 arguments

useQuery(QUERY_KEY, FETCH_FUNCTION, { OPTIONS });
Enter fullscreen mode Exit fullscreen mode
  • QUERY_KEY is just a key that can uniquely identify a query across your application. You might come up with why the identification of any query is required? It is required as the data from the API might get outdated or the query response gets invalid, React-Query will trigger a re-fetch automatically when any of the above happens so it needs to know which query to re-fetch.

As per React query docs:

React Query manages query caching for you based on query keys. Query keys have to be an Array at the top level, and can be as simple as an Array with a single string, or as complex as an array of many strings and nested objects. As long as the query key is serializable, and unique to the query's data, you can use it!

  • FETCH_FUNCTION your data fetching logic goes here

  • OPTIONS object here we can configure our query response like stale time, cache time, enabled, etc. More on this in the next lessons.

useQuery hook returns various methods and variables that represent the query request phases and response like:

data: (3) [{}, {}, {}]
dataUpdatedAt: 1654966057499
error: null
errorUpdateCount: 0
errorUpdatedAt: 0
failureCount: 0
isError: false
isFetched: true
isFetchedAfterMount: true
isFetching: false
isIdle: false
isLoading: false
isLoadingError: false
isPlaceholderData: false
isPreviousData: false
isRefetchError: false
isRefetching: false
isStale: true
isSuccess: true
refetch: ƒ ()
remove: ƒ ()
status: "success"
Enter fullscreen mode Exit fullscreen mode

You can go through these but at the moment we only require the isLoading and isError states. You can see here how react query internally handles all the states automatically without any issues.
For the query response you can have a look at React-Query Devtools as well, just click on the query key and the details of that query will be opened as below

devtools screenshot


Thank you for reading!

That’s how React-Query abstracts away a lot of data fetching and management logic from the component.
In the next chapter, we will dive into the configuration options that we can use to configure the query using the options parameter in the useQuery hook.

Feel free to reach out to me! 😊

💻 Github ✨ Twitter 💌 Email 💡Linkedin

Till then happy coding!

Top comments (0)