DEV Community

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

Posted on

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! πŸŽ‰βœ¨

Top comments (0)