Skip to content

Simplify Data Fetching with TanStack Queries

Published: at 04:47 AM

Most of the time, we obtain data from external sources, which is a core aspect of frontend applications. There are numerous libraries available for data retrieval, but as applications grow larger and more complex, we need a library that can handle these situations and help us avoid writing thousands of lines of unmaintainable code. In this post, I will demonstrate how to use queries and mutations from TanStack Query.

Table of contents

Open Table of contents

1. Why TanStack Query?

TanStack Query fulfills most web application needs, such as fetching, caching, synchronizing, and updating server state. Moreover, after migrating from React Query to TanStack Query, it now supports most popular frameworks, including React, Vue, Angular, Solid, and Svelte. So, if you are working with different technologies, this package will make your life easier.

2. Installing the Package

You can install TanStack Query using npm, pnpm, yarn, and bun.

npm i @tanstack/react-query
# or
pnpm add @tanstack/react-query
# or
yarn add @tanstack/react-query
# or
bun add @tanstack/react-query

3. Queries

We’ll start with useQuery, which is responsible for querying asynchronous data. When you use useQuery, you need two things:

The unique key is used for caching purposes. The great thing about useQuery is that it returns several crucial states to keep us informed, such as isPending, isError, data, and error. Here is an example:

// action.ts
"use server";

import { db } from "@/db";
import { getKindeServerSession } from "@kinde-oss/kinde-auth-nextjs/server";

export const getAuthStatus = async () => {
  const { getUser } = getKindeServerSession();
  const user = await getUser()

  if (!user?.id || !user.email) {
    throw new Error("Invalid user data")
  }

  const existingUser = await db.user.findFirst({
    where: {
      id: user.id,
    }
  })

  if(!existingUser) {
    await db.user.create({
        data: {
            id: user.id,
            email: user.email,
        }
    })
  }

  return { success: true };
};

// page.tsx
const Todos = () => {
  const { isPending, isError, data, error } = useQuery({
    queryKey: ['auth-callback'],
    queryFn: async () => await getAuthStatus(),
  })

  if (isPending) {
    return <span>Loading...</span>
  }

  if (isError) {
    return <span>Error: {error.message}</span>
  }

  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}

If the query fails and throws an error, TanStack Query automatically retries the query (by default, it retries 3 times). There is also a retry delay so that it doesn’t immediately re-execute after failing. Here is how you can modify the useQuery:

const Todos = () => {
  const { isPending, isError, data, error } = useQuery({
    queryKey: ['auth-callback'],
    queryFn: async () => await getAuthStatus(),
    retry: 5,
    retryDelay: 500,
  })

  if (isPending) {
    return <span>Loading...</span>
  }

  if (isError) {
    return <span>Error: {error.message}</span>
  }

  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}

4. Mutations

Unlike queries, mutations are used for POST, PUT, and DELETE methods or performing server-side effects. Here is an example of using mutations to hit an API from our API routes.

  const mutation = useMutation({
    mutationKey: ["save-todo"],
    mutationFn: (newTodo) => {
      const data = axios.post('/todos', newTodo)
      const response = data.json()
      return { name: response.name }
    },
    onSuccess: ({ name }) => {
    // if success
    toast({
        title: "Todo Created",
    });
    },
    onError: () => {
    // if error
      toast({
        title: "Something went wrong!",
        description: "There was an error on our end. Please try again later.",
        variant: "destructive",
      });
    },
  });
  })

Just like useQuery, useMutation has an option to retry calls if they fail. After successfully calling the API, we need to update the data to reflect the changes in the UI. For this, we can use setQueryData. Here’s how you can do it:

import { useMutation, useQueryClient } from "@tanstack/react-query";

  const queryClient = useQueryClient();
  const mutation = useMutation({
    mutationFn: (newTodo) => {
      const data = axios.post('/todos', newTodo)
      const response = data.json()
      return { name: response.name }
    },
    onSuccess: (data) => {
    // if success
    toast({
        title: "Todo Created",
    });
    queryClient.setQueryData(["get-todo"], (oldTodo) => [
        data,
        ...oldTodo,
    ]);
    },
    onError: () => {
    // if error
      toast({
        title: "Something went wrong!",
        description: "There was an error on our end. Please try again later.",
        variant: "destructive",
      });
    },
  });
  })

That’s it for this post. I hope this helps. See you in the next one!