mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 10:11:13 -05:00
Refactor: Implement API and cache improvements
This commit is contained in:
173
src/docs/API_PATTERNS.md
Normal file
173
src/docs/API_PATTERNS.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# API and Cache Patterns
|
||||
|
||||
## Mutation Pattern (PREFERRED)
|
||||
|
||||
Always use `useMutation` hooks for data modifications instead of direct Supabase calls.
|
||||
|
||||
### ✅ CORRECT Pattern
|
||||
|
||||
```typescript
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { toast } from 'sonner';
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
import { useQueryInvalidation } from '@/lib/queryInvalidation';
|
||||
|
||||
export function useMyMutation() {
|
||||
const queryClient = useQueryClient();
|
||||
const { invalidateRelatedCache } = useQueryInvalidation();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (params) => {
|
||||
const { error } = await supabase
|
||||
.from('table')
|
||||
.insert(params);
|
||||
|
||||
if (error) throw error;
|
||||
},
|
||||
onMutate: async (params) => {
|
||||
// Optional: Optimistic updates
|
||||
await queryClient.cancelQueries({ queryKey: ['my-data'] });
|
||||
const previous = queryClient.getQueryData(['my-data']);
|
||||
|
||||
queryClient.setQueryData(['my-data'], (old) => {
|
||||
// Update optimistically
|
||||
});
|
||||
|
||||
return { previous };
|
||||
},
|
||||
onError: (error, variables, context) => {
|
||||
// Rollback optimistic updates
|
||||
if (context?.previous) {
|
||||
queryClient.setQueryData(['my-data'], context.previous);
|
||||
}
|
||||
|
||||
toast.error("Error", {
|
||||
description: getErrorMessage(error),
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
invalidateRelatedCache();
|
||||
toast.success("Success", {
|
||||
description: "Operation completed successfully.",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ INCORRECT Pattern (Direct Supabase)
|
||||
|
||||
```typescript
|
||||
// DON'T DO THIS
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const { error } = await supabase.from('table').insert(data);
|
||||
if (error) throw error;
|
||||
toast.success('Success');
|
||||
} catch (error) {
|
||||
toast.error(error.message);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Error Handling Pattern
|
||||
|
||||
### ✅ CORRECT: Use onError callback
|
||||
|
||||
```typescript
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (data) => {
|
||||
const { error } = await supabase.from('table').insert(data);
|
||||
if (error) throw error;
|
||||
},
|
||||
onError: (error: unknown) => {
|
||||
toast.error("Error", {
|
||||
description: getErrorMessage(error),
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### ❌ INCORRECT: try/catch in component
|
||||
|
||||
```typescript
|
||||
// Avoid this pattern
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await mutation.mutateAsync(data);
|
||||
} catch (error) {
|
||||
// Error already handled in mutation
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Query Keys Pattern
|
||||
|
||||
### ✅ CORRECT: Use centralized queryKeys
|
||||
|
||||
```typescript
|
||||
import { queryKeys } from '@/lib/queryKeys';
|
||||
|
||||
const { data } = useQuery({
|
||||
queryKey: queryKeys.parks.detail(slug),
|
||||
queryFn: fetchParkDetail,
|
||||
});
|
||||
```
|
||||
|
||||
### ❌ INCORRECT: Inline query keys
|
||||
|
||||
```typescript
|
||||
// Don't do this
|
||||
const { data } = useQuery({
|
||||
queryKey: ['parks', 'detail', slug],
|
||||
queryFn: fetchParkDetail,
|
||||
});
|
||||
```
|
||||
|
||||
## Cache Invalidation Pattern
|
||||
|
||||
### ✅ CORRECT: Use invalidation helpers
|
||||
|
||||
```typescript
|
||||
import { useQueryInvalidation } from '@/lib/queryInvalidation';
|
||||
|
||||
const { invalidateParks, invalidateHomepageData } = useQueryInvalidation();
|
||||
|
||||
// In mutation onSuccess:
|
||||
onSuccess: () => {
|
||||
invalidateParks();
|
||||
invalidateHomepageData('parks');
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ INCORRECT: Manual invalidation
|
||||
|
||||
```typescript
|
||||
// Avoid this
|
||||
queryClient.invalidateQueries({ queryKey: ['parks'] });
|
||||
```
|
||||
|
||||
## Benefits of This Pattern
|
||||
|
||||
1. **Automatic retry logic**: Failed mutations can be retried automatically
|
||||
2. **Loading states**: `isPending` flag for UI feedback
|
||||
3. **Optimistic updates**: Update UI before server confirms
|
||||
4. **Consistent error handling**: Centralized error handling
|
||||
5. **Cache coordination**: Proper invalidation timing
|
||||
6. **Testing**: Easier to mock and test
|
||||
7. **Type safety**: Better TypeScript support
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
When migrating a component:
|
||||
|
||||
- [ ] Create custom mutation hook in appropriate directory
|
||||
- [ ] Use `useMutation` instead of direct Supabase calls
|
||||
- [ ] Implement `onError` callback with toast notifications
|
||||
- [ ] Implement `onSuccess` callback with cache invalidation
|
||||
- [ ] Use centralized `queryKeys` for query identification
|
||||
- [ ] Use `useQueryInvalidation` helpers for cache management
|
||||
- [ ] Replace loading state with `mutation.isPending`
|
||||
- [ ] Remove try/catch blocks from component
|
||||
- [ ] Test optimistic updates if applicable
|
||||
Reference in New Issue
Block a user