I’ve Used TanStack Query for 5+ Years — Here’s Why I Still Recommend It to Every Frontend Dev

Written on 2026-07-14 by Adam Drake - 7 min read

Image of I’ve Used TanStack Query for 5+ Years — Here’s Why I Still Recommend It to Every Frontend Dev

Medium Member?

My Medium friends can read this story over on Medium.

I was lucky enough to move house recently and be blessed with a swimming pool. As the weather heats up for summer it’s an absolute god send. One thing I have realised pretty quickly though is that pools get dirty fast.

I did some research online and found the Scuba S1 Pro from Aiper. It blew me away when I used it. It’s built with quality in mind and does a great job of cleaning the pool. It wasn’t cheap but it was very much worth it. When something works really well it’s just so satisfying.

I get the same feeling when I use Tanstack Query. I have been using it for years now and it’s my go-to library for all things data fetching related. I recommend it to all developers who are getting into frontend, it’s such a better experience than trying to do the same thing without this library. Let’s dive into why it’s so good and why I recommend you try it out on your next project.

What Is Tanstack Query?

Tanstack Query is a library that helps you handle data fetching in your application. It comes with some very sensible defaults and has a bunch of useful functionality for you out of the box so you don’t have to worry about it.

We’re talking about things like cache, query keys to help with invalidating and refetching fresh data and a whole lot more.

I’ve been using Tanstack Query since the days it was called ‘React Query’ so it’s stayed with me through thick and thin. I trust it completely. A big reason for this is because of the man behind the whole Tanstack suite — Tanner Linsley — Whenever I hear him speak, his approach is always extremely sensible and well thought through. It’s obvious he thinks deeply about his work and he is the type of person who is able to clearly see the heart of the problem and go straight after it (a rare gift these days).

Let’s get into why you should use this library in your next project.

Reduces Boilerplate For Fetching Data

I’m going to focus on the useQuery hook. This is a hook which you can give any Promise based method (GET or POST) to fetch data from a server.

First lets look at an example in React of fetching a TODO from a server the traditional way:

function Todo({ id }: TodoProps) {
  const [todo, setTodo] = useState<Todo | null>(null);
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    let cancelled = false;
    setIsLoading(true);
    setError(null);

    fetch(`https://api.example.com/todos/${id}`)
      .then((res) => {
        if (!res.ok) throw new Error("Failed to fetch todo");
        return res.json() as Promise<Todo>;
      })
      .then((data) => {
        if (!cancelled) setTodo(data);
      })
      .catch((err: unknown) => {
        if (!cancelled) {
          setError(err instanceof Error ? err : new Error("Unknown error"));
        }
      })
      .finally(() => {
        if (!cancelled) setIsLoading(false);
      });

    return () => {
      cancelled = true;
    };
  }, [id]);

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  if (!todo) return null;

  return <p>{todo.title}</p>;
}

With this approach you have to do a few things yourself:

  • Handle loading state
  • Handle error state
  • Run everything through a useEffect so the data is fetched when the component loads

When using the useQuery hook the initial code is reduced and — in my opinion — is much more readable:

function Todo({ id }: TodoProps) {
  const { data: todo, isLoading, error } = useQuery<Todo, Error>({
    queryKey: ["todo", id],
    queryFn: async () => {
      const res = await fetch(`https://api.example.com/todos/${id}`);
      if (!res.ok) throw new Error("Failed to fetch todo");
      return res.json() as Promise<Todo>;
    },
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  if (!todo) return null;

  return <p>{todo.title}</p>;
}

Notice a few things here:

  • A queryKey is provided to the useQuery hook — we’ll get back to this as it’s very useful later on when we get to invalidating queries.
  • Loading and error state are handled by the useQuery hook so we don’t have to worry about managing it ourselves.
  • The useQuery hook is typed so we know exactly what to expect in each potential scenario.
  • We’re doing the same thing in about half as much code.

It’s Very Customisable

Tanstack chooses very sensible defaults out of the box and for many standard api requests it doesn’t require much tweaking. However, there are a bunch of knobs you can tweak and tune if you need to.

One great example is refetchOnWindowFocus — this option defaults to true but very often I don’t want this behaviour. I work on apps where the data doesn’t update too often and the user navigates around many different browser windows.

When the user returns back to the original app it was very annoying that the data was refetching every time.

With the tweak of this option I could turn it off and the data would only refetch after the stale time was reached.

It’s as easy as adding in fields to the object passed into the useQuery hook.

const { data: todo, isLoading, error } = useQuery<Todo, Error>({
    queryKey: ["todo", id],
    queryFn: async () => {
      const res = await fetch(`https://api.example.com/todos/${id}`);
      if (!res.ok) throw new Error("Failed to fetch todo");
      return res.json() as Promise<Todo>;
    },
    refetchOnWindowFocus: false,
    staleTime: 1000 * 60 * 5, // 5 minutes
  });

For the full set of options you can see the useQuery api resource.

It Comes With Cache

Tanstack query comes with default caching. It comes with a default gcTime (used to be cacheTime in v4) of 5 minutes and the default staleTime of 0. gcTime is the time in milliseconds that unused/inactive cache data remains in memory. staleTime is the time in milliseconds after data is considered stale.

For normal queries I don’t usually change these settings as they give you the functionality you need. But recently I’ve had use cases where I needed to use an api service to grab images that are generated on the fly. However, once the image is generated it doesn’t need to change. Therefore I set the gcTime and staleTime to Infinity so once the image is fetched the first time it never needs to be fetched again. The cache’s garbage collection is disabled and the data is never considered to be stale. The app can keep using the same image on the UI whenever it needs it.

This is quite a niche use case but the fact you can control how long the data in a query should be cached is very useful.

Able To Invalidate Queries

Invalidating queries becomes very useful as your app grows. Let’s say you have a page which uses the useQuery hook to fetch some data. That hook will have a key — something like [‘todos’] — this key acts as something that Tanstack Query can utilise when it needs to.

On another part of the UI you are updating some data and you want to make sure once the data is successfully updated the “todos” data is refetched. To do this you can invalidate this key “todos” so the query is then ‘stale’. If this current data is rendered on the UI it will trigger Tanstack to refetch the data. The code look something like this.

const queryClient = useQueryClient()

const mutation = useMutation({
  mutationFn: updateTodo,
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
})

The power to be able to retrigger the fetching of specific data depending on different user interactions is very powerful, especially across larger dynamic applications and it’s something that I use often.

Conclusion

Choosing the right tool for the job is part of being good at your craft and it’s best learnt with experience. In software it’s no different except maybe the sheer range of tools to choose from.

Tanstack Query is one of those stand out tools that I think any developer working on the frontend should be aware of. It’s default functionality covers most standard use cases but it also comes packed with a load of customisable elements.

The creator — Tanner Linsley — has his head screwed on and speaks a great deal of sense, which is sometimes lacking in this crazy fast paced world we have created. When you find people like this in your industry, I advise you to keep a close eye on what they’re working on and always try out their creations.


Subscribe to My Weekly Updates on Medium!

Enjoyed This Post?

If you found this blog post helpful, why not stay updated with my latest content? Subscribe to receive email notifications every time I publish.

If you're feeling really generous you can buy me a coffee. (Btw, I really like coffee…)

What You'll Get

  • Exciting Discoveries: Be the first to know about the latest tools and libraries.
  • How-To Guides: Step-by-step articles to enhance your development skills.
  • Opinion Pieces: Thought-provoking insights into the world of frontend development.

Join Our Community

I live in the vibrant city of Prague, Czech Republic, with my family. My blog is more than just articles; it's a community of like-minded developers who share a love for innovation and learning.

Adam Drake AI Selfie

Written by Adam Drake

Adam Drake is a Frontend React Developer who is very passionate about the quality of the web. He lives with his wife and three children in Prague in the Czech Republic.

Adam Drakes Site © 2026