platform-codebase/@packages/@hooks/react-query-utils/EXAMPLES.md

7.6 KiB
Executable file

Usage Examples

useMutationOptions - Standardized Error Handling

Basic Example

import { useMutation } from '@tanstack/react-query';
import { useMutationOptions } from '@lilith/react-query-utils';
import { apiClient } from './api';

function useCreateUser() {
  const options = useMutationOptions({
    operation: 'create user',
    successMessage: 'User created successfully!',
    invalidateKeys: [['users']],
  });

  return useMutation({
    mutationFn: (data) => apiClient.post('/users', data),
    ...options,
  });
}

// In component
function CreateUserForm() {
  const { mutate: createUser, isPending } = useCreateUser();

  const handleSubmit = (data: CreateUserDto) => {
    createUser(data);
    // ✅ Automatically shows success toast
    // ✅ Automatically invalidates ['users'] query
    // ✅ Automatically logs errors to console
  };
}

Real-World Example: User Management

import { useMutation } from '@tanstack/react-query';
import { useMutationOptions } from '@lilith/react-query-utils';
import { useNavigate } from 'react-router-dom';
import { userApi } from './api/users';

// Create User Hook
export function useCreateUser() {
  const navigate = useNavigate();

  const options = useMutationOptions<User, CreateUserDto>({
    operation: 'create user',
    successMessage: 'Welcome aboard! Account created successfully.',
    invalidateKeys: [['users'], ['stats', 'user-count']],
    onSuccess: (user) => {
      // Custom logic after success
      navigate(`/users/${user.id}`);
    },
  });

  return useMutation({
    mutationFn: userApi.create,
    ...options,
  });
}

// Update User Hook
export function useUpdateUser() {
  const options = useMutationOptions<User, { id: string; data: UpdateUserDto }>({
    operation: 'update user',
    successMessage: 'Profile updated successfully!',
    invalidateKeys: [
      ['users'],
      ['users', 'id'], // Will be replaced with actual ID
    ],
  });

  return useMutation({
    mutationFn: ({ id, data }) => userApi.update(id, data),
    ...options,
  });
}

// Delete User Hook
export function useDeleteUser() {
  const options = useMutationOptions<void, string>({
    operation: 'delete user',
    successMessage: 'User deleted successfully.',
    invalidateKeys: [['users'], ['stats', 'user-count']],
    onSuccess: () => {
      // Redirect to users list after deletion
      window.location.href = '/users';
    },
  });

  return useMutation({
    mutationFn: userApi.delete,
    ...options,
  });
}

Example: Silent Operations

// No toast notification for background operations
export function useSyncUserSettings() {
  const options = useMutationOptions({
    operation: 'sync settings',
    successMessage: false, // No toast
    invalidateKeys: [['settings']],
    enableErrorLogging: false, // No console logs
  });

  return useMutation({
    mutationFn: settingsApi.sync,
    ...options,
  });
}

Example: Complex Invalidation

// Invalidate multiple related queries
export function usePublishPost() {
  const options = useMutationOptions({
    operation: 'publish post',
    successMessage: 'Your post is now live!',
    invalidateKeys: [
      ['posts'], // All posts
      ['posts', 'published'], // Published posts filter
      ['user', 'me', 'posts'], // Current user's posts
      ['stats', 'post-count'], // Post count stats
    ],
  });

  return useMutation({
    mutationFn: postApi.publish,
    ...options,
  });
}

Example: Custom Error Handling

import { analytics } from './analytics';

export function useSubscribeToPlan() {
  const options = useMutationOptions({
    operation: 'subscribe to plan',
    successMessage: 'Subscription activated!',
    invalidateKeys: [['subscription'], ['user', 'me']],
    onSuccess: (subscription) => {
      // Track successful subscription
      analytics.track('Subscription Created', {
        planId: subscription.planId,
        amount: subscription.amount,
      });
    },
    onError: (error) => {
      // Custom error tracking
      if (error.response?.status === 402) {
        analytics.track('Payment Failed', {
          reason: error.response.data.message,
        });
      }
    },
  });

  return useMutation({
    mutationFn: subscriptionApi.create,
    ...options,
  });
}

Before/After Comparison

Before: Manual Error Handling (70 lines)

import { useMutation, useQueryClient } from '@tanstack/react-query';
import { getErrorMessage } from '@lilith/api-client';
import toast from 'react-hot-toast';

export function useCreateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data) => apiClient.post('/users', data),
    onSuccess: (user) => {
      toast.success('User created successfully!');
      queryClient.invalidateQueries({ queryKey: ['users'] });
      queryClient.invalidateQueries({ queryKey: ['stats', 'user-count'] });
    },
    onError: (error) => {
      const message = getErrorMessage(error);
      toast.error(message || 'Failed to create user');
      console.error('[create user] Error:', error);
    },
  });
}

export function useUpdateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: ({ id, data }) => apiClient.patch(`/users/${id}`, data),
    onSuccess: (user, variables) => {
      toast.success('Profile updated successfully!');
      queryClient.invalidateQueries({ queryKey: ['users'] });
      queryClient.invalidateQueries({ queryKey: ['users', variables.id] });
    },
    onError: (error) => {
      const message = getErrorMessage(error);
      toast.error(message || 'Failed to update user');
      console.error('[update user] Error:', error);
    },
  });
}

export function useDeleteUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (id) => apiClient.delete(`/users/${id}`),
    onSuccess: () => {
      toast.success('User deleted successfully.');
      queryClient.invalidateQueries({ queryKey: ['users'] });
      queryClient.invalidateQueries({ queryKey: ['stats', 'user-count'] });
    },
    onError: (error) => {
      const message = getErrorMessage(error);
      toast.error(message || 'Failed to delete user');
      console.error('[delete user] Error:', error);
    },
  });
}

After: Using useMutationOptions (30 lines - 57% reduction)

import { useMutation } from '@tanstack/react-query';
import { useMutationOptions } from '@lilith/react-query-utils';

export function useCreateUser() {
  const options = useMutationOptions({
    operation: 'create user',
    successMessage: 'User created successfully!',
    invalidateKeys: [['users'], ['stats', 'user-count']],
  });

  return useMutation({
    mutationFn: (data) => apiClient.post('/users', data),
    ...options,
  });
}

export function useUpdateUser() {
  const options = useMutationOptions({
    operation: 'update user',
    successMessage: 'Profile updated successfully!',
    invalidateKeys: [['users']],
  });

  return useMutation({
    mutationFn: ({ id, data }) => apiClient.patch(`/users/${id}`, data),
    ...options,
  });
}

export function useDeleteUser() {
  const options = useMutationOptions({
    operation: 'delete user',
    successMessage: 'User deleted successfully.',
    invalidateKeys: [['users'], ['stats', 'user-count']],
  });

  return useMutation({
    mutationFn: (id) => apiClient.delete(`/users/${id}`),
    ...options,
  });
}

Improvements:

  • 57% less code
  • Standardized error handling across all mutations
  • Consistent toast notifications
  • Automatic error logging
  • Type-safe with TypeScript generics
  • Easy to customize with callbacks