# Cache Invalidation Quick Reference Guide

## Decision Tree

```mermaid
graph TD
    A[Data Changed] --> B{What Changed?}
    B -->|Profile| C[Profile Invalidation]
    B -->|Park| D[Park Invalidation]
    B -->|Ride| E[Ride Invalidation]
    B -->|Company| F[Company Invalidation]
    B -->|User Action| G[Security Invalidation]
    
    C --> C1[invalidateUserProfile]
    C --> C2[invalidateProfileStats]
    C --> C3[invalidateProfileActivity]
    C --> C4{Name Changed?}
    C4 -->|Yes| C5[invalidateUserSearch]
    
    D --> D1[invalidateParks]
    D --> D2[invalidateParkDetail]
    D --> D3[invalidateParkRides]
    D --> D4[invalidateHomepage]
    
    E --> E1[invalidateRides]
    E --> E2[invalidateRideDetail]
    E --> E3[invalidateParkRides]
    E --> E4[invalidateHomepage]
    
    F --> F1[invalidateCompanies]
    F --> F2[invalidateCompanyDetail]
    
    G --> G1[invalidateUserProfile]
    G --> G2[invalidateAuditLogs]
```

## Quick Lookup Table

| Action | Invalidate These | Helper Function |
|--------|------------------|-----------------|
| **Update Profile** | User profile, stats, activity | `invalidateUserProfile()`, `invalidateProfileStats()`, `invalidateProfileActivity()` |
| **Change Username** | + User search | `invalidateUserSearch()` |
| **Change Avatar** | User profile only | `invalidateUserProfile()` |
| **Update Privacy** | Profile, search | `invalidateUserProfile()`, `invalidateUserSearch()` |
| **Create Park** | All parks, homepage | `invalidateParks()`, `invalidateHomepage()` |
| **Update Park** | Parks, detail, homepage | `invalidateParks()`, `invalidateParkDetail()`, `invalidateHomepage()` |
| **Delete Park** | Parks, rides, homepage | `invalidateParks()`, `invalidateParkRides()`, `invalidateHomepage()` |
| **Create Ride** | Rides, park rides, homepage | `invalidateRides()`, `invalidateParkRides()`, `invalidateHomepage()` |
| **Update Ride** | Rides, detail, park rides, homepage | `invalidateRides()`, `invalidateRideDetail()`, `invalidateParkRides()`, `invalidateHomepage()` |
| **Delete Ride** | Rides, park rides, homepage | `invalidateRides()`, `invalidateParkRides()`, `invalidateHomepage()` |
| **Create Company** | All companies | `invalidateCompanies()` |
| **Update Company** | Companies, detail | `invalidateCompanies()`, `invalidateCompanyDetail()` |
| **Block User** | User profile, blocklist, search | `invalidateUserProfile()`, `invalidateBlockedUsers()`, `invalidateUserSearch()` |
| **Unblock User** | Same as block | Same as block |
| **Change Password** | Sessions (optional) | `invalidateSessions()` |
| **Change Email** | Profile, email status | `invalidateUserProfile()`, `invalidateEmailStatus()` |
| **Update Role** | Profile, permissions | `invalidateUserProfile()` |
| **Submit Moderation** | Queue, entity | `invalidateModerationQueue()`, `invalidate[Entity]()` |
| **Approve/Reject** | Queue, entity, homepage | `invalidateModerationQueue()`, `invalidate[Entity]()`, `invalidateHomepage()` |
| **Add Ride Credit** | Ride credits, profile stats | `invalidateRideCredits()`, `invalidateProfileStats()` |
| **Reorder Credits** | Ride credits only | `invalidateRideCredits()` |

## Common Patterns

### Pattern 1: Simple Entity Update

**Use Case**: Updating a single entity with no relations

```typescript
export function useSimpleUpdateMutation() {
  const { invalidateEntity } = useQueryInvalidation();
  
  return useMutation({
    mutationFn: updateEntity,
    onSuccess: (_data, { slug }) => {
      invalidateEntity(slug);
      
      toast.success("Updated");
    }
  });
}
```

**Examples**: Update park name, update ride description

### Pattern 2: Entity Update with Relations

**Use Case**: Updating entity that affects parent/child relations

```typescript
export function useRelatedUpdateMutation() {
  const { 
    invalidateRideDetail,
    invalidateParkRides,
    invalidateHomepage 
  } = useQueryInvalidation();
  
  return useMutation({
    mutationFn: updateRide,
    onSuccess: (_data, { rideSlug, parkSlug }) => {
      invalidateRideDetail(rideSlug);  // The ride itself
      invalidateParkRides(parkSlug);   // Parent park's rides
      invalidateHomepage();            // Recent changes feed
      
      toast.success("Updated");
    }
  });
}
```

**Examples**: Update ride (affects park), update park (affects rides)

### Pattern 3: User Action with Audit Trail

**Use Case**: Actions that affect user and create audit logs

```typescript
export function useAuditedActionMutation() {
  const { 
    invalidateUserProfile,
    invalidateAuditLogs,
    invalidateUserSearch 
  } = useQueryInvalidation();
  
  return useMutation({
    mutationFn: performAction,
    onSuccess: (_data, { userId }) => {
      invalidateUserProfile(userId);   // User's profile
      invalidateAuditLogs(userId);     // Audit trail
      invalidateUserSearch();          // Search results
      
      toast.success("Action completed");
    }
  });
}
```

**Examples**: Block user, change role, update privacy

### Pattern 4: Create with List Update

**Use Case**: Creating new entity that appears in lists

```typescript
export function useCreateMutation() {
  const { 
    invalidateParks,
    invalidateHomepage 
  } = useQueryInvalidation();
  
  return useMutation({
    mutationFn: createPark,
    onSuccess: () => {
      invalidateParks();        // All park lists
      invalidateHomepage();     // Recent changes
      
      toast.success("Created");
    }
  });
}
```

**Examples**: Create park, create ride, create company

### Pattern 5: Conditional Invalidation

**Use Case**: Invalidation depends on what changed

```typescript
export function useConditionalMutation() {
  const { 
    invalidateUserProfile,
    invalidateProfileStats,
    invalidateUserSearch 
  } = useQueryInvalidation();
  
  return useMutation({
    mutationFn: updateProfile,
    onSuccess: (_data, { userId, updates }) => {
      // Always invalidate profile
      invalidateUserProfile(userId);
      
      // Conditional invalidations
      if (updates.display_name || updates.username) {
        invalidateUserSearch();  // Name changed
      }
      
      if (updates.avatar_url) {
        invalidateProfileStats(userId);  // Avatar in stats
      }
      
      toast.success("Updated");
    }
  });
}
```

**Examples**: Profile update, privacy update

## Entity-Specific Guides

### Parks

#### Create Park
```typescript
invalidateParks();           // Global list + owner lists
invalidateHomepage();        // Recent changes
```

#### Update Park
```typescript
invalidateParks();           // All lists
invalidateParkDetail(slug);  // Detail page
invalidateHomepage();        // Recent changes
```

#### Delete Park
```typescript
invalidateParks();           // All lists
invalidateParkRides(slug);   // Park's rides (cleanup)
invalidateHomepage();        // Recent changes
```

### Rides

#### Create Ride
```typescript
invalidateRides();           // Global list + manufacturer lists
invalidateParkRides(parkSlug); // Parent park
invalidateHomepage();        // Recent changes
```

#### Update Ride
```typescript
invalidateRides();           // All lists
invalidateRideDetail(slug);  // Detail page
invalidateParkRides(parkSlug); // Parent park
invalidateHomepage();        // Recent changes
```

#### Delete Ride
```typescript
invalidateRides();           // All lists
invalidateParkRides(parkSlug); // Parent park
invalidateHomepage();        // Recent changes
```

### Companies

#### Create Company
```typescript
invalidateCompanies();       // Global list
```

#### Update Company
```typescript
invalidateCompanies();       // All lists
invalidateCompanyDetail(slug); // Detail page
```

### Users & Profiles

#### Update Profile
```typescript
invalidateUserProfile(userId);
invalidateProfileStats(userId);
invalidateProfileActivity(userId);

// If name changed:
if (nameChanged) {
  invalidateUserSearch();
}
```

#### Update Privacy
```typescript
invalidateUserProfile(userId);
invalidateUserSearch();      // Visibility changed
```

#### Block/Unblock User
```typescript
invalidateUserProfile(targetUserId);
invalidateUserProfile(actorUserId);  // Your blocklist
invalidateBlockedUsers(actorUserId);
invalidateUserSearch();
```

### Security Actions

#### Change Password
```typescript
// Optional: invalidate sessions to force re-login elsewhere
invalidateSessions(userId);
```

#### Change Email
```typescript
invalidateUserProfile(userId);
invalidateEmailStatus();  // Email verification status
```

#### Update Role
```typescript
invalidateUserProfile(userId);
// Note: App should re-fetch user permissions
```

### Moderation

#### Submit for Moderation
```typescript
invalidateModerationQueue();
// Don't invalidate entity yet - not approved
```

#### Approve Submission
```typescript
invalidateModerationQueue();
invalidate[Entity]();        // Park/Ride/Company
invalidateHomepage();        // Recent changes
invalidateUserProfile(submitterId); // Stats
```

#### Reject Submission
```typescript
invalidateModerationQueue();
// Don't invalidate entity - rejected
```

### Ride Credits

#### Add Credit
```typescript
invalidateRideCredits(userId);
invalidateProfileStats(userId);
```

#### Reorder Credits
```typescript
invalidateRideCredits(userId);
// Stats unaffected - just order
```

#### Remove Credit
```typescript
invalidateRideCredits(userId);
invalidateProfileStats(userId);
```

## Anti-Patterns (Don't Do This!)

### ❌ Over-Invalidation
```typescript
// BAD: Invalidating everything
onSuccess: () => {
  queryClient.invalidateQueries(); // Nukes entire cache!
}

// GOOD: Specific invalidation
onSuccess: () => {
  invalidateUserProfile(userId);
}
```

### ❌ Under-Invalidation
```typescript
// BAD: Forgetting related data
onSuccess: () => {
  invalidateRideDetail(slug); // Only detail page
  // Missing: invalidateRides() for lists
  // Missing: invalidateParkRides() for parent
}

// GOOD: All affected queries
onSuccess: () => {
  invalidateRideDetail(slug);
  invalidateRides();
  invalidateParkRides(parkSlug);
  invalidateHomepage();
}
```

### ❌ Manual Cache Manipulation
```typescript
// BAD: Manually updating cache
onSuccess: (newData) => {
  queryClient.setQueryData(['park', slug], newData);
}

// GOOD: Let React Query refetch
onSuccess: () => {
  invalidateParkDetail(slug); // Triggers refetch
}
```

### ❌ Wrong Query Keys
```typescript
// BAD: Hardcoded strings
queryClient.invalidateQueries({ queryKey: ['profile'] });

// GOOD: Using centralized keys
import { queryKeys } from '@/lib/queryKeys';
queryClient.invalidateQueries({ queryKey: queryKeys.profile.detail(userId) });
```

### ❌ Missing Optimistic Rollback
```typescript
// BAD: Optimistic update without rollback
onMutate: (newData) => {
  queryClient.setQueryData(key, newData);
  // Missing: return previous data
},
onError: () => {
  toast.error("Failed");
  // Missing: rollback
}

// GOOD: Proper optimistic pattern
onMutate: (newData) => {
  const previous = queryClient.getQueryData(key);
  queryClient.setQueryData(key, newData);
  return { previous }; // Save for rollback
},
onError: (err, vars, context) => {
  queryClient.setQueryData(key, context.previous); // Rollback
  toast.error("Failed");
}
```

## Testing Invalidation

### Unit Test Example
```typescript
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

it('should invalidate correct queries on success', async () => {
  const queryClient = new QueryClient();
  const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
  
  const { result } = renderHook(
    () => useProfileUpdateMutation(),
    {
      wrapper: ({ children }) => (
        <QueryClientProvider client={queryClient}>
          {children}
        </QueryClientProvider>
      )
    }
  );
  
  await result.current.mutateAsync({ 
    userId: 'test-id',
    updates: { display_name: 'New Name' }
  });
  
  await waitFor(() => {
    expect(invalidateSpy).toHaveBeenCalledWith({
      queryKey: ['profile', 'test-id']
    });
  });
});
```

### Manual Test Checklist
1. Open React Query DevTools
2. Perform mutation
3. Check that affected queries show as "invalidated"
4. Verify queries refetch automatically
5. Confirm UI updates with new data

## Quick Reference: Import Statements

```typescript
// Invalidation helpers
import { useQueryInvalidation } from '@/lib/queryInvalidation';

// In your hook:
const { 
  invalidateUserProfile,
  invalidateParks,
  invalidateRides,
  invalidateHomepage
} = useQueryInvalidation();

// Query keys (for manual invalidation)
import { queryKeys } from '@/lib/queryKeys';
import { useQueryClient } from '@tanstack/react-query';

const queryClient = useQueryClient();
queryClient.invalidateQueries({ 
  queryKey: queryKeys.profile.detail(userId) 
});
```

## When in Doubt

**General Rule**: If data X changes, invalidate:
1. Data X itself (detail view)
2. All lists containing X (list views)
3. Any parent/child relations (related entities)
4. Homepage/feeds if it might appear there
5. Search results if searchable fields changed

**Example Thinking Process**:
- "I'm updating a ride"
- "The ride detail page shows this" → invalidate ride detail
- "The all-rides list shows this" → invalidate rides list
- "The park's rides list shows this" → invalidate park rides
- "The homepage shows recent changes" → invalidate homepage
- "Done!"

---

For more details, see:
- [API_PATTERNS.md](./API_PATTERNS.md) - How to structure mutations
- [CACHE_DEBUGGING.md](./CACHE_DEBUGGING.md) - Troubleshooting issues
- [PRODUCTION_READY.md](./PRODUCTION_READY.md) - Architecture overview
