DEV Community

Cover image for Centralizing Query and Mutation Configurations with TanStack Query + React 😎
Juan Castillo
Juan Castillo

Posted on

1

Centralizing Query and Mutation Configurations with TanStack Query + React 😎

TanStack Query is a powerful tool for managing server state in React applications. However, configuring queries and mutations repeatedly in a project can lead to code duplication and inconsistency. In this tutorial, we’ll centralize the configuration of queries and mutations using two custom hooks: useApiGet and useApiSend. πŸš€

These hooks will:

  1. Provide consistent retry logic.
  2. Handle success and error scenarios.
  3. Allow query invalidation post-mutation.
  4. Minimize boilerplate across your project.

Why Centralize Query and Mutation Configurations?

Centralizing these configurations ensures:

Reusability: Use the same logic for all queries and mutations.
Consistency: Avoid discrepancies in retry mechanisms or error handling.
Maintainability: Update logic in one place and propagate it across your application.

The Code

Here’s the implementation of the useApiGet and useApiSend hooks:

1. Custom Query Hook: useApiGet

This hook simplifies useQuery with retry logic and default options.

import { isAxiosError } from 'axios';
import { useQuery, QueryKey, QueryFunction } from '@tanstack/react-query';

const HTTP_STATUS_TO_NOT_RETRY = [400, 401, 403, 404, 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511];
const MAX_RETRY = 3;

type ApiGetProps = {
  key: QueryKey;
  fn: Promise<any> | QueryFunction<unknown, QueryKey, never> | undefined;
  options?: any;
};

const useApiGet = <T>({ key, fn, options }: ApiGetProps) =>
  useQuery<T>({
    queryKey: key,
    queryFn: fn,
    refetchOnWindowFocus: false,
    retry: (failureCount, error: any) => {
      if (failureCount >= MAX_RETRY) return false;
      if (isAxiosError(error) && HTTP_STATUS_TO_NOT_RETRY.includes(error.response?.status ?? 0)) return false;
      return true;
    },
    ...options,
  });
Enter fullscreen mode Exit fullscreen mode

Key Features:

  • Retry Mechanism: Retries up to MAX_RETRY times for network issues.
  • Avoids Retry on Certain Status Codes: Stops retrying for non-network errors like 404 or 500.
  • Customizable Options: Allows overriding default behaviors via the options prop.

2. Custom Mutation Hook: useApiSend

This hook builds on useMutation and includes features like query invalidation and callbacks for success/error handling.

import { useMutation, useQueryClient, InvalidateQueryFilters } from '@tanstack/react-query';
import { isAxiosError } from 'axios';

type ApiSendProps<T> = {
  fn: ((data: T) => Promise<any>) | (() => Promise<any>);
  success?: (data: any) => void;
  error?: (error: any) => void;
  invalidateKey?: InvalidateQueryFilters[] | undefined;
  options?: any;
};

const useApiSend = <T>({ fn, success, error, invalidateKey, options }: ApiSendProps<T>) => {
  const queryClient = useQueryClient();

  return useMutation<unknown, Error, T>({
    mutationFn: fn,
    onSuccess: (data) => {
      if (invalidateKey) {
        invalidateKey.forEach((key) => queryClient.invalidateQueries(key));
      }
      if (success) success(data);
    },
    onError: error,
    retry: (failureCount, error: any) => {
      if (failureCount > 2) return false;
      if (isAxiosError(error) && HTTP_STATUS_TO_NOT_RETRY.includes(error.response?.status ?? 0)) return false;
      return true;
    },
    ...options,
  });
};
Enter fullscreen mode Exit fullscreen mode

Key Features:

  • Query Invalidation: Automatically refreshes specific queries when a mutation succeeds.
  • Custom Callbacks: Executes success or error callbacks for further handling.
  • Retry Logic: Similar to useApiGet, retries only on network-related errors.

How to Use These Hooks

Fetching Data with useApiGet
Use this hook to fetch data consistently across your application.

import { useApiGet } from './apiHooks';

const UserProfile = () => {
  const { data, isLoading, error } = useApiGet({
    key: ['user', 'profile'],
    fn: axios.get('/api/profile'),
  });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error loading profile</p>;

  return <div>{data?.name}</div>;
};
Enter fullscreen mode Exit fullscreen mode

Sending Data with useApiSend

Use this hook to send data while managing invalidation and success/error callbacks.

import { useApiSend } from './apiHooks';

const UpdateProfile = () => {
  const { mutate, isLoading } = useApiSend({
    fn: (data) => axios.post('/api/profile', data),
    success: () => alert('Profile updated successfully!'),
    error: (err) => console.error('Failed to update profile:', err),
    invalidateKey: [['user', 'profile']],
  });

  const handleSubmit = () => {
    mutate({ name: 'New Name' });
  };

  return (
    <button onClick={handleSubmit} disabled={isLoading}>
      Update Profile
    </button>
  );
};

Enter fullscreen mode Exit fullscreen mode

Conclusion

Centralizing the configuration of queries and mutations using TanStack Query is a game-changer for managing server state in React applications. With useApiGet and useApiSend, you can streamline logic, improve consistency, and reduce code duplication.

Start implementing these hooks today, and let TanStack Query simplify your state management journey!. Happy coding! πŸŽ‰βœ¨

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free β†’

πŸ‘‹ Kindness is contagious

Please leave a ❀️ or a friendly comment on this post if you found it helpful!

Okay