Back to Blog Posts

WTF is React Query ⁉️

March 19, 2024

I recently implemented TanStack's TanStack Query (React Query) into a project of mine to see what all the fuss was about. Seemingly every react webapp I looked at was running it. Why? Was it that revolutionary? At my day job we got by 'fine' with just classic data fetching and redux.

It Is Awesome

Simply put React Query is awesome. It takes so much of the React caveats around data fetching you have to implement over and over throughout a codebase and abstracts it almost completely away.

To really appreciate what React Query does for a React project, let's take a step back. What is React in the first place?

React is a library for building UIs. It can really be broken down to the formula view = function(state). All we as React developers have to worry about is the state of our applications, and React handles the rest. But the visual portion of a UI is only part of the battle, as you may know, a React project contains a ton of non-visual logic as well. This is why React has hooks.

From useMemo to useRef hooks are there to facilitate reusable logic in React apps. But what hook do we have to fetch data? Arguably the most popular non-visual logic needed for modern webapps. Well, since useData isn't a thing, most developers rely heavily on useEffect to fetch data then store it through useState. Even starter React devs likely have seen the following before.

import * as React from "react"

export default function App () {
  const [data, setData] = React.useState(null)

  React.useEffect(() => {
    const handleFetchData = async () => {
      setData(null)

      const res = await fetch(`https://myapi.com/api/data`)
      const json = await res.json()
      setData(json)
    }

    handleFetchData()
  }, [])

  return (
    <>
      <MyAwesomeComponent data={data} />
    </>
  )
}

Now this code 'works' but just barely. Some of the main concerns of this starter code are as follows.

There is no loading state for the component, so when it loads in, it will be jarring.

Now we can solve this by storing the loading state and rendering a loading skeleton or spinner accordingly ny passing it to the component.

import * as React from "react"

export default function App () {
  const [data, setData] = React.useState(null)
  const [isLoading, setIsLoading] = React.useState(true)

  React.useEffect(() => {
    const handleFetchData = async () => {
      setData(null)
      setIsLoading(true)

      const res = await fetch(`https://myapi.com/api/data`)
      const json = await res.json()
      setData(json)
      setIsLoading(true)
    }

    handleFetchData()
  }, [])

  return (
    <>
      <MyAwesomeComponent isLoading={isLoading} data={data} />
    </>
  )
}

What if on the off chance the request to our awesome api fails?

This would be bad, essentially we would never set isLoading back to true, and the user would die of old age waiting for our component. 💔 Now you can prevent this by storing an error in state as well then handling it in the component.

And that only scratches the surface unfortunately. What if we send multiple requests when a user spams a button and our component doesn't know how to handle data coming back out of order? What about caching? Invalidation? AHHHHH!

Enter React Query

This is where React Query swoops in to save the day. All of those concerns—loading states, error handling, deduplication of requests, caching, invalidation, and more—are built into React Query out of the box. The best part? It wraps all this functionality into a simple, declarative API that feels like magic ✨.

Let’s rewrite our example with React Query and see how much cleaner it becomes.

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

export default function App() {
  const { data, isLoading, error } = useQuery(["awesomeData"], async () => {
    const res = await fetch("https://myapi.com/api/data");
    if (!res.ok) throw new Error("Failed to fetch data");
    return res.json();
  });

  if (isLoading) return <LoadingSpinner />;
  if (error) return <ErrorComponent message={error.message} />;

  return <MyAwesomeComponent data={data} />;
}

Boom! 🚀 We’ve gone from a spaghetti mess of state management to a concise, readable, and robust implementation.

Plus plus plus, React Query has an amazing DevTools package, an infinite scroll handler, mutations, query invalidation, and a bunch more!