platform-codebase/@packages/@hooks/react-query-utils/QUICK_START.md
2026-01-18 09:20:13 -08:00

5.4 KiB
Executable file

Quick Start Guide - useMutationOptions

Installation

Already included in @lilith/react-query-utils

Basic Usage

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

function useCreateItem() {
  const options = useMutationOptions({
    operation: 'create item',
    invalidateKeys: [['items']],
  });

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

Common Patterns

Pattern 1: Simple CRUD

// Create
const options = useMutationOptions({
  operation: 'create user',
  invalidateKeys: [['users']],
});

// Update
const options = useMutationOptions({
  operation: 'update user',
  invalidateKeys: [['users'], ['users', userId]],
});

// Delete
const options = useMutationOptions({
  operation: 'delete user',
  invalidateKeys: [['users']],
});

Pattern 2: Custom Messages

const options = useMutationOptions({
  operation: 'publish post',
  successMessage: 'Your post is now live!', // Custom
  invalidateKeys: [['posts']],
});

// OR disable message
const options = useMutationOptions({
  operation: 'sync settings',
  successMessage: false, // No toast
  invalidateKeys: [['settings']],
});

Pattern 3: Multiple Invalidations

const options = useMutationOptions({
  operation: 'complete task',
  invalidateKeys: [
    ['tasks'],           // All tasks
    ['tasks', taskId],   // Specific task
    ['stats'],           // Stats dashboard
  ],
});

Pattern 4: Custom Callbacks

const options = useMutationOptions({
  operation: 'upload file',
  onSuccess: (file) => {
    navigate(`/files/${file.id}`);
  },
  onError: (error) => {
    analytics.track('Upload Failed', { error });
  },
});

Pattern 5: Type Safety

interface User {
  id: string;
  name: string;
}

interface CreateUserDto {
  name: string;
  email: string;
}

const options = useMutationOptions<User, CreateUserDto>({
  operation: 'create user',
  onSuccess: (user) => {
    // user is typed as User
    console.log(user.id);
  },
});

Configuration Options

Option Type Default Description
operation string Required Operation name (e.g., "create user")
successMessage string | false Capitalized operation Toast message or false to disable
invalidateKeys Array<string | string[]> undefined Query keys to invalidate
onSuccess (data) => void undefined Custom success handler
onError (error) => void undefined Custom error handler
enableErrorLogging boolean true Log errors to console

What It Does Automatically

Success Handling:

  • Shows toast notification
  • Invalidates specified queries
  • Calls custom callbacks

Error Handling:

  • Extracts error message from API response
  • Shows error toast
  • Logs to console (optional)
  • Calls custom error handler

Type Safety:

  • Full TypeScript support
  • Generic types for data and variables
  • Compatible with React Query types

Before/After Example

Before (Manual)

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

  return useMutation({
    mutationFn: (data) => apiClient.post('/users', data),
    onSuccess: (user) => {
      toast.success('User created!');
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
    onError: (error) => {
      toast.error(getErrorMessage(error));
      console.error('[create user]:', error);
    },
  });
}

After (With useMutationOptions)

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

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

Result: 60% less code, standardized behavior

Tips

💡 Tip 1: Use descriptive operation names

// Good
operation: 'create user account'
operation: 'publish blog post'
operation: 'complete checkout'

// Avoid
operation: 'create'
operation: 'update'
operation: 'submit'

💡 Tip 2: Invalidate related queries

// When updating a user, invalidate both list and detail
invalidateKeys: [
  ['users'],        // List query
  ['users', userId] // Detail query
]

💡 Tip 3: Disable logging in production

const options = useMutationOptions({
  operation: 'sync data',
  enableErrorLogging: import.meta.env.DEV, // Only in dev
});

💡 Tip 4: Use custom callbacks for navigation

const navigate = useNavigate();

const options = useMutationOptions({
  operation: 'create post',
  onSuccess: (post) => {
    navigate(`/posts/${post.id}`);
  },
});

Troubleshooting

Q: Toast not showing? A: Make sure react-hot-toast is set up in your app with <Toaster /> component.

Q: Queries not invalidating? A: Check that query keys match exactly (array equality is strict).

Q: TypeScript errors? A: Provide generic types: useMutationOptions<DataType, VariablesType>({ ... })

Q: Want to add loading states? A: Use isPending from the mutation: const { mutate, isPending } = useMutation({ ... })

See Also