---
name: apollo-client
description: "writing or modifying GraphQL…"
---

# 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
