295 lines
7.6 KiB
Markdown
295 lines
7.6 KiB
Markdown
|
|
# Usage Examples
|
||
|
|
|
||
|
|
## useMutationOptions - Standardized Error Handling
|
||
|
|
|
||
|
|
### Basic Example
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 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
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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)
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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)
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
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
|