Home / News / Globally Manage Toast Notifications with Tanstack Query

Globally Manage Toast Notifications with Tanstack Query

There’s little doubt that Tanstack Query is the modern go-to library for handling async data fetching and state management. Formerly known as React Query, Tanstack Query has seen significant adoption in modern web development, particularly within the React ecosystem, but also expanding to other frameworks like Vue, Solid, and Svelte. It abstracts away much of the complexity involved in fetching, caching, synchronizing, and updating server-side data in client-side applications. This includes handling loading states, errors, background re-fetching, and data invalidation. For all these reasons, it’s been widely adopted here at Atomic Object.

The Problem

Today I’d like to show you a lesser-known technique for reducing boilerplate code when performing a mutation operation. A common pattern you might see in a typical client-side application is this:


const mutation = useMutation({
  mutationFn: createTodo,
  onSuccess: (data) => {
     queryClient.invalidateQueries({ queryKey: ['todos'] });
     toast.success("Todo created successfullu")
  },
  onError: (error) => {
    toast.error("Something went wrong white creating todo!")
  },
});

As you can see, as you write more mutations there will be a lot of repeated code: the onSuccess / onError callback function, the cache invalidation and the triggering of the error or success toast messages.

A Solution

This can all be simplified with a slightly different approach involving some global configuration. Using a custom MutationCache object and configuring it’s onSuccess and onError callbacks to display the toasts with a custom message specified in your useMutation call. You can also specify when queries to invalidate


import {
  MutationCache,
  QueryClient,
  QueryKey,
} from "@tanstack/react-query";
import { toast } from "sonner";

declare module "@tanstack/react-query" {
  interface Register {
    mutationMeta: {
      skipToast?: boolean;
      invalidatesQuery?: QueryKey;
      successMessage?: string;
      successTitle?: string;
      errorMessage?: string;
      errorTitle?: string;
    };
  }
}

export const queryClient = new QueryClient({
  mutationCache: new MutationCache({
    onSuccess: (_data, _variables, _context, mutation) => {
      if (mutation.meta?.successMessage && !mutation.meta?.skipToast) {
        toast.success(mutation.meta.successMessage);
      }
    },
    onError: (_error, _variables, _context, mutation) => {
      console.log("Mutation error:", _error);
      if (mutation.meta?.errorMessage && !mutation.meta?.skipToast) {
        toast.error(mutation.meta.errorMessage);
      }
    },
    onSettled: (_data, _error, _variables, _context, mutation) => {
      if (mutation.meta?.invalidatesQuery) {
        queryClient.invalidateQueries({
          queryKey: mutation.meta.invalidatesQuery,
        });
      }
    },
  }),
});

The Solution In Action

Now, when performing a mutation, your code becomes much cleaner and more declarative:


const mutation = useMutation({
  mutationFn: createTodo,
  meta: {
    invalidatesQuery: ['todos'],
    successMessage: 'Todo created successfully',
    errorMessage: 'Something when wrong while creating todo!' 
  }
});

Or, should you wish to skip showing the toast messages entirely in some cases:


const mutation = useMutation({
  mutationFn: createTodo,
  meta: {
    invalidatesQuery(['todos']),
    skipToast: true,
  }
});

Benefits and Extensions

This pattern offers several advantages:

  • Consistency: All mutations follow the same pattern for error handling and cache invalidation
  • Maintainability: Changes to toast behavior or invalidation logic can be made in one place
  • Flexibility: Individual mutations can still opt out of global behavior when needed
  • Cleaner Code: Mutation definitions focus on their core purpose rather than cross-cutting concerns

With a few additional changes, this setup could be extended to invalidate multiple queries, specify toast titles along with custom messages, or even include more sophisticated error handling logic. This makes it a very powerful pattern to have in your toolkit when building web applications that use TanStack Query with several mutations and toast notifications.

The post Globally Manage Toast Notifications with Tanstack Query appeared first on Atomic Spin.

Tagged:

Leave a Reply

Your email address will not be published. Required fields are marked *