# cache-invalidation: Use Targeted Invalidation Over Broad Patterns

## Priority: CRITICAL

## Explanation

Query invalidation marks cached data as stale, triggering background refetches. Use targeted invalidation to refresh only affected data. Overly broad invalidation causes unnecessary network requests; too narrow invalidation leaves stale data.

## Bad Example

```tsx
// Invalidating everything after a single todo update
const mutation = useMutation({
  mutationFn: updateTodo,
  onSuccess: () => {
    queryClient.invalidateQueries(); // Invalidates ENTIRE cache
  },
});

// Invalidating too broadly
const mutation = useMutation({
  mutationFn: updateTodoStatus,
  onSuccess: () => {
    // Invalidates all todos including unrelated lists
    queryClient.invalidateQueries({ queryKey: ["todos"] });
  },
});

// Missing invalidation of related queries
const mutation = useMutation({
  mutationFn: addComment,
  onSuccess: () => {
    // Only invalidates comment list, misses comment count
    queryClient.invalidateQueries({ queryKey: ["comments", postId] });
  },
});
```

## Good Example

```tsx
// Targeted invalidation with exact matching
const mutation = useMutation({
  mutationFn: updateTodo,
  onSuccess: (data, variables) => {
    // Invalidate specific todo and related queries
    queryClient.invalidateQueries({ queryKey: ["todos", variables.id] });
    // Also invalidate lists that might contain this todo
    queryClient.invalidateQueries({ queryKey: ["todos", "list"] });
  },
});

// Use exact: true when you only want one specific query
const mutation = useMutation({
  mutationFn: updateUserProfile,
  onSuccess: () => {
    queryClient.invalidateQueries({
      queryKey: ["user", "profile"],
      exact: true, // Only this exact key, not ['user', 'profile', 'settings']
    });
  },
});

// Invalidate multiple related queries
const mutation = useMutation({
  mutationFn: addComment,
  onSuccess: (data, { postId }) => {
    // Invalidate all comment-related queries for this post
    queryClient.invalidateQueries({ queryKey: ["posts", postId, "comments"] });
    queryClient.invalidateQueries({ queryKey: ["posts", postId, "comment-count"] });
    // Optionally invalidate the post itself if it shows comment count
    queryClient.invalidateQueries({ queryKey: ["posts", postId] });
  },
});

// Predicate-based invalidation for complex scenarios
queryClient.invalidateQueries({
  predicate: query => query.queryKey[0] === "todos" && query.state.data?.userId === currentUserId,
});
```

## Invalidation Patterns

```tsx
// Prefix matching (default) - invalidates all matching prefixes
queryClient.invalidateQueries({ queryKey: ["todos"] });
// Matches: ['todos'], ['todos', 1], ['todos', { status: 'done' }]

// Exact matching - only the exact key
queryClient.invalidateQueries({ queryKey: ["todos"], exact: true });
// Matches: ['todos'] only

// Predicate matching - custom logic
queryClient.invalidateQueries({
  predicate: query => query.queryKey.includes("user-generated"),
});

// Refetch type control
queryClient.invalidateQueries({
  queryKey: ["todos"],
  refetchType: "active", // Only refetch active queries (default)
  // refetchType: 'inactive' - Only inactive
  // refetchType: 'all' - Both
  // refetchType: 'none' - Mark stale but don't refetch
});
```

## Context

- Invalidation only marks queries as stale; refetch happens when query is used
- `refetchType: 'active'` (default) only refetches queries with active observers
- Use hierarchical query keys to enable precise invalidation
- Consider `setQueryData` for optimistic updates instead of invalidation
- Always test invalidation patterns to ensure all affected queries are refreshed
