# 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