---
name: apollo-client
description: This skill should be used when writing or modifying GraphQL operations, hooks, or mutations using Apollo Client 3.10. It enforces best practices for optimistic responses, cache updates, and TypeScript type generation. Use this skill when creating new queries/mutations, reviewing Apollo code, or troubleshooting cache issues.
---

# Apollo Client 3.10

## Overview

This skill provides best practices for Apollo Client 3.10 in this codebase, ensuring consistent patterns for GraphQL operations, optimistic UI updates, cache management, and TypeScript type safety.

## Quick Reference

### Generator Commands

After modifying any `operations.graphql` file, run the appropriate generator:

```bash
bun run generate:types:dev        # Development environment
bun run generate:types:staging    # Staging environment
bun run generate:types:production # Production environment
```

> **Note:** Replace `bun` with your project's package manager (`npm`, `yarn`, `pnpm`) as needed.

### Import Pattern

All GraphQL types, hooks, and documents must come from generated types:

```typescript
import {
  useGetPlayerQuery,
  useUpdatePlayerMutation,
  GetPlayerQuery,
  PlayerFragment,
  ListPlayersDocument,
} from "@/generated/graphql";
```

## Core Rules

### 1. Only Use Generated Types

Import all GraphQL-related types from `@/generated/graphql`. Never define manual TypeScript types for GraphQL entities.

```typescript
// CORRECT
import { PlayerFragment, useUpdatePlayerMutation } from "@/generated/graphql";

// INCORRECT - Never do this
type Player = { id: string; name: string };
```

### 2. Never Modify Generated Files

The `generated/graphql.ts` file is auto-generated by codegen. To change types:

1. Edit the appropriate `operations.graphql` file in the feature directory
2. Run `bun run generate:types:dev`
3. Import the newly generated types

### 3. Always Run Generator After Schema Changes

After modifying any `operations.graphql` file, immediately run the generator before committing. Verify changes compile with `bun run typecheck`.

### 4. Always Include Optimistic Response

Every mutation must include an `optimisticResponse` for instant UI feedback:

```typescript
const [updatePlayer] = useUpdatePlayerMutation({
  optimisticResponse: variables => ({
    __typename: "Mutation",
    updatePlayer: {
      __typename: "Player",
      id: variables.id,
      name: variables.input.name,
      updatedAt: new Date().toISOString(),
    },
  }),
});
```

Key requirements:

- Always include `__typename` for every object in the response
- Always include `id` for cache normalization
- Use temporary IDs for new objects (e.g., `crypto.randomUUID()`)
- Include all fields that the mutation returns

### 5. Always Update Cache

Every mutation must handle cache updates using one of these strategies:

**Automatic Updates** - When mutation returns the full entity with `id` and `__typename`, Apollo updates automatically. No extra code needed.

**cache.modify** - For adding/removing items from lists:

```typescript
const [addPlayer] = useAddPlayerMutation({
  optimisticResponse: {
    /* ... */
  },
  update(cache, { data }) {
    cache.modify({
      fields: {
        players(existingPlayers = [], { readField }) {
          const newRef = cache.writeFragment({
            data: data.addPlayer,
            fragment: PlayerFragmentDoc,
          });
          return [...existingPlayers, newRef];
        },
      },
    });
  },
});
```

**refetchQueries** - Fallback for complex scenarios:

```typescript
const [complexMutation] = useComplexMutation({
  refetchQueries: ["ListPlayers"],
  awaitRefetchQueries: true,
});
```

## Operations.graphql Structure

### Fragment-First Pattern

Define fragments before queries/mutations that use them:

```graphql
# 1. Fragments first
fragment PlayerFragment on Player {
  id
  knownName
  firstName
  lastName
  team {
    id
    name
  }
}

# 2. Queries second
query GetPlayer($id: ID!) {
  player(id: $id) {
    ...PlayerFragment
  }
}

# 3. Mutations last
mutation UpdatePlayer($id: ID!, $input: UpdatePlayerInput!) {
  updatePlayer(id: $id, input: $input) {
    ...PlayerFragment
  }
}
```

### Include Required Fields

Mutations must return all fields needed for cache updates:

```graphql
mutation AddPlayerToKanban($input: AddPlayerToKanbanInput!) {
  addPlayerToKanban(input: $input) {
    id # Required for cache normalization
    position
    notes
    kanbanPhaseId
    kanbanPhase {
      # Include related objects
      id
      name
    }
    createdAt
    updatedAt
  }
}
```

## Query Best Practices

### Fetch Policies

```typescript
// Frequently changing data - balance speed and freshness
fetchPolicy: "cache-and-network";

// Stable reference data - prioritize cache
fetchPolicy: "cache-first";

// Always-fresh data - skip cache
fetchPolicy: "network-only";
```

### Skip When Variables Undefined

```typescript
const { data } = useGetPlayerQuery({
  variables: { id: playerId! },
  skip: !playerId,
});
```

### Error Handling

Use `onError` callback instead of try/catch with console.log:

```typescript
const [mutation] = useMutation(MUTATION, {
  onError: error => {
    setErrorState("Failed to update. Please try again.");
  },
});
```

## Complete Mutation Pattern

Reference `references/mutation-patterns.md` for comprehensive examples of the complete mutation pattern including optimistic responses, cache updates, and error handling.

## Validation Checklist

When writing or reviewing Apollo code, verify:

- [ ] All types imported from `@/generated/graphql`
- [ ] No manual type definitions for GraphQL entities
- [ ] Every mutation has `optimisticResponse`
- [ ] Every mutation has cache update strategy
- [ ] `__typename` included in all optimistic response objects
- [ ] `id` included in all optimistic response objects
- [ ] Queries use appropriate `fetchPolicy`
- [ ] Queries use `skip` when variables may be undefined
- [ ] Error handling via `onError` callback
- [ ] Generator was run after operations.graphql changes
