Back to skills
SkillHub ClubWrite Technical DocsFull StackFrontendTech Writer

apollo-client

Guide for building React applications with Apollo Client 4.x. Use this skill when: (1) setting up Apollo Client in a React project, (2) writing GraphQL queries or mutations with hooks, (3) configuring caching or cache policies, (4) managing local state with reactive variables, (5) troubleshooting Apollo Client errors or performance issues.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
36
Hot score
90
Updated
March 20, 2026
Overall rating
C3.0
Composite score
3.0
Best-practice grade
C64.8

Install command

npx @skill-hub/cli install apollographql-skills-apollo-client

Repository

apollographql/skills

Skill path: skills/apollo-client

Guide for building React applications with Apollo Client 4.x. Use this skill when: (1) setting up Apollo Client in a React project, (2) writing GraphQL queries or mutations with hooks, (3) configuring caching or cache policies, (4) managing local state with reactive variables, (5) troubleshooting Apollo Client errors or performance issues.

Open repository

Best for

Primary workflow: Write Technical Docs.

Technical facets: Full Stack, Frontend, Tech Writer.

Target audience: everyone.

License: MIT.

Original source

Catalog source: SkillHub Club.

Repository owner: apollographql.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install apollo-client into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/apollographql/skills before adding apollo-client to shared team environments
  • Use apollo-client for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: apollo-client
description: >
  Guide for building React applications with Apollo Client 4.x. Use this skill when:
  (1) setting up Apollo Client in a React project,
  (2) writing GraphQL queries or mutations with hooks,
  (3) configuring caching or cache policies,
  (4) managing local state with reactive variables,
  (5) troubleshooting Apollo Client errors or performance issues.
license: MIT
compatibility: React 18+, React 19 (Suspense/RSC). Works with Next.js, Vite, CRA, and other React frameworks.
metadata:
  author: apollographql
  version: "1.0.0"
allowed-tools: Bash(npm:*) Bash(npx:*) Bash(node:*) Read Write Edit Glob Grep
---

# Apollo Client 4.x Guide

Apollo Client is a comprehensive state management library for JavaScript that enables you to manage both local and remote data with GraphQL. Version 4.x brings improved caching, better TypeScript support, and React 19 compatibility.

## Integration Guides

Choose the integration guide that matches your application setup:

- **[Client-Side Apps](references/integration-client.md)** - For client-side React applications without SSR (Vite, Create React App, etc.)
- **[Next.js App Router](references/integration-nextjs.md)** - For Next.js applications using the App Router with React Server Components
- **[React Router Framework Mode](references/integration-react-router.md)** - For React Router 7 applications with streaming SSR
- **[TanStack Start](references/integration-tanstack-start.md)** - For TanStack Start applications with modern routing

Each guide includes installation steps, configuration, and framework-specific patterns optimized for that environment.

## Quick Reference

### Basic Query

```tsx
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
    }
  }
`;

function UserProfile({ userId }: { userId: string }) {
  const { loading, error, data, dataState } = useQuery(GET_USER, {
    variables: { id: userId },
  });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  // TypeScript: dataState === "ready" provides better type narrowing than just checking data
  return <div>{data.user.name}</div>;
}
```

### Basic Mutation

```tsx
import { gql } from "@apollo/client";
import { useMutation } from "@apollo/client/react";

const CREATE_USER = gql`
  mutation CreateUser($input: CreateUserInput!) {
    createUser(input: $input) {
      id
      name
    }
  }
`;

function CreateUserForm() {
  const [createUser, { loading, error }] = useMutation(CREATE_USER);

  const handleSubmit = async (name: string) => {
    await createUser({ variables: { input: { name } } });
  };

  return <button onClick={() => handleSubmit("John")}>Create User</button>;
}
```

### Suspense Query

```tsx
import { Suspense } from "react";
import { useSuspenseQuery } from "@apollo/client/react";

function UserProfile({ userId }: { userId: string }) {
  const { data } = useSuspenseQuery(GET_USER, {
    variables: { id: userId },
  });

  return <div>{data.user.name}</div>;
}

function App() {
  return (
    <Suspense fallback={<p>Loading user...</p>}>
      <UserProfile userId="1" />
    </Suspense>
  );
}
```

## Reference Files

Detailed documentation for specific topics:

- [TypeScript Code Generation](references/typescript-codegen.md) - GraphQL Code Generator setup for type-safe operations
- [Queries](references/queries.md) - useQuery, useLazyQuery, polling, refetching
- [Suspense Hooks](references/suspense-hooks.md) - useSuspenseQuery, useBackgroundQuery, useReadQuery, useLoadableQuery
- [Mutations](references/mutations.md) - useMutation, optimistic UI, cache updates
- [Fragments](references/fragments.md) - Fragment colocation, useFragment, useSuspenseFragment, data masking
- [Caching](references/caching.md) - InMemoryCache, typePolicies, cache manipulation
- [State Management](references/state-management.md) - Reactive variables, local state
- [Error Handling](references/error-handling.md) - Error policies, error links, retries
- [Troubleshooting](references/troubleshooting.md) - Common issues and solutions

## Key Rules

### Query Best Practices

- **Each page should generally only have one query, composed from colocated fragments.** Use `useFragment` or `useSuspenseFragment` in all non-page-components. Use `@defer` to allow slow fields below the fold to stream in later and avoid blocking the page load.
- **Fragments are for colocation, not reuse.** Each fragment should describe exactly the data needs of a specific component, not be shared across components for common fields. See [Fragments reference](references/fragments.md) for details on fragment colocation and data masking.
- Always handle `loading` and `error` states in UI when using non-suspenseful hooks (`useQuery`, `useLazyQuery`). When using Suspense hooks (`useSuspenseQuery`, `useBackgroundQuery`), React handles this through `<Suspense>` boundaries and error boundaries.
- Use `fetchPolicy` to control cache behavior per query
- Use the TypeScript type server to look up documentation for functions and options (Apollo Client has extensive docblocks)

### Mutation Best Practices

- **If the schema permits, mutation return values should return everything necessary to update the cache.** Neither manual updates nor refetching should be necessary.
- If the mutation response is insufficient, carefully weigh manual cache manipulation vs refetching. Manual updates risk missing server logic. Consider optimistic updates with a granular refetch if needed.
- Handle errors gracefully in the UI
- Use `refetchQueries` sparingly (prefer letting the cache update automatically)

### Caching Best Practices

- Configure `keyFields` for types without `id` field
- Disable normalization by setting `keyFields: false` for types that don't include an identifier and are meant to group related fields under the parent
- Use `typePolicies` for pagination and computed fields
- Understand cache normalization to debug issues
- **Enable [data masking](references/fragments.md#data-masking) for all new applications** - it prevents components from accessing fragment data they don't own, enforcing proper data boundaries and preventing over-rendering

### Performance

- Avoid over-fetching with proper field selection
- Configure appropriate `fetchPolicy` per use case
- Use `@defer` for incremental delivery of deferred query parts, and `@stream` for streaming list fields (`@stream` available in Apollo Client 4.1+)
- **Prefer Suspense hooks (`useSuspenseQuery`, `useBackgroundQuery`) in modern applications** for better loading state handling and code simplicity

## Ground Rules

- ALWAYS use Apollo Client 4.x patterns (not v3 or earlier)
- ALWAYS wrap your app with `ApolloProvider`
- ALWAYS handle loading and error states when using non-suspenseful hooks
- PREFER Suspense hooks (`useSuspenseQuery`, `useBackgroundQuery`) in modern applications for better DX
- NEVER store Apollo Client in React state (use module-level or context)
- PREFER `cache-first` for read-heavy data, `network-only` for real-time data
- USE TypeScript for better type safety with GraphQL
- IMPLEMENT proper cache updates instead of refetching entire queries
- ADVISE the human steering you to look into Apollo DevTools when collaboratively debugging Apollo Client issues


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/integration-client.md

```markdown
# Apollo Client Integration for Client-Side Apps

This guide covers setting up Apollo Client in client-side React applications without server-side rendering (SSR). This includes applications using Vite, Parcel, Create React App, or other bundlers that don't implement SSR.

For applications with SSR, use one of the framework-specific integration guides instead:

- [Next.js App Router](integration-nextjs.md)
- [React Router Framework Mode](integration-react-router.md)
- [TanStack Start](integration-tanstack-start.md)

## Installation

```bash
npm install @apollo/client graphql rxjs
```

## TypeScript Code Generation (optional but recommended)

For type-safe GraphQL operations with TypeScript, see the [TypeScript Code Generation guide](typescript-codegen.md).

## Setup Steps

### Step 1: Create Client

```typescript
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";

// Recommended: Use HttpOnly cookies for authentication
const httpLink = new HttpLink({
  uri: "https://your-graphql-endpoint.com/graphql",
  credentials: "include", // Sends cookies with requests (secure when using HttpOnly cookies)
});

const client = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache(),
});
```

If you need manual token management (less secure, only when HttpOnly cookies aren't available):

```typescript
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
import { SetContextLink } from "@apollo/client/link/context";

const httpLink = new HttpLink({
  uri: "https://your-graphql-endpoint.com/graphql",
});

const authLink = new SetContextLink(({ headers }) => {
  const token = localStorage.getItem("token");
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    },
  };
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
});
```

### Step 2: Setup Provider

```tsx
import { ApolloProvider } from "@apollo/client";
import App from "./App";

function Root() {
  return (
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
  );
}
```

### Step 3: Execute Query

```tsx
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";

const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      email
    }
  }
`;

function UserList() {
  const { loading, error, data, dataState } = useQuery(GET_USERS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  // TypeScript: dataState === "ready" provides better type narrowing than just checking data
  return (
    <ul>
      {data.users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
```

## Basic Query Usage

### Using Variables

```tsx
const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`;

function UserProfile({ userId }: { userId: string }) {
  const { loading, error, data, dataState } = useQuery(GET_USER, {
    variables: { id: userId },
  });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  // TypeScript: dataState === "ready" provides better type narrowing than just checking data
  return <div>{data.user.name}</div>;
}
```

> **Note for TypeScript users**: Use [`dataState`](https://www.apollographql.com/docs/react/data/typescript#type-narrowing-data-with-datastate) for more robust type safety and better type narrowing in Apollo Client 4.x.

### TypeScript Integration

For complete examples with loading, error handling, and `dataState` for type narrowing, see [Basic Query Usage](#basic-query-usage) above.

#### Usage with Generated Types

For type-safe operations with code generation, see the [TypeScript Code Generation guide](typescript-codegen.md).

Quick example:

```typescript
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";
import { GetUserDocument } from "./queries.generated";

// Define your query with the if (false) pattern for code generation
if (false) {
  gql`
    query GetUser($id: ID!) {
      user(id: $id) {
        id
        name
        email
      }
    }
  `;
}

function UserProfile({ userId }: { userId: string }) {
  // Types are automatically inferred from GetUserDocument
  const { data } = useQuery(GetUserDocument, {
    variables: { id: userId },
  });

  return <div>{data.user.name}</div>;
}
```

#### Usage with Manual Type Annotations

If not using code generation, define types alongside your queries using `TypedDocumentNode`:

```typescript
import { gql, TypedDocumentNode } from "@apollo/client";
import { useQuery } from "@apollo/client/react";

interface GetUserData {
  user: {
    id: string;
    name: string;
    email: string;
  };
}

interface GetUserVariables {
  id: string;
}

// Types should always be defined via TypedDocumentNode alongside your queries/mutations, not at the useQuery/useMutation call site
const GET_USER: TypedDocumentNode<GetUserData, GetUserVariables> = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`;

function UserProfile({ userId }: { userId: string }) {
  const { data } = useQuery(GET_USER, {
    variables: { id: userId },
  });

  // data.user is automatically typed from GET_USER
  return <div>{data.user.name}</div>;
}
```

## Basic Mutation Usage

```tsx
import { gql, TypedDocumentNode } from "@apollo/client";
import { useMutation } from "@apollo/client/react";

interface CreateUserMutation {
  createUser: {
    id: string;
    name: string;
    email: string;
  };
}

interface CreateUserMutationVariables {
  input: {
    name: string;
    email: string;
  };
}

const CREATE_USER: TypedDocumentNode<CreateUserMutation, CreateUserMutationVariables> = gql`
  mutation CreateUser($input: CreateUserInput!) {
    createUser(input: $input) {
      id
      name
      email
    }
  }
`;

function CreateUserForm() {
  const [createUser, { loading, error }] = useMutation(CREATE_USER);

  const handleSubmit = async (formData: FormData) => {
    const { data } = await createUser({
      variables: {
        input: {
          name: formData.get("name") as string,
          email: formData.get("email") as string,
        },
      },
    });
    if (data) {
      console.log("Created user:", data.createUser);
    }
  };

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        handleSubmit(new FormData(e.currentTarget));
      }}
    >
      <input name="name" placeholder="Name" />
      <input name="email" placeholder="Email" />
      <button type="submit" disabled={loading}>
        {loading ? "Creating..." : "Create User"}
      </button>
      {error && <p>Error: {error.message}</p>}
    </form>
  );
}
```

## Client Configuration Options

```typescript
const client = new ApolloClient({
  // Required: The cache implementation
  cache: new InMemoryCache({
    typePolicies: {
      Query: {
        fields: {
          // Field-level cache configuration
        },
      },
    },
  }),

  // Network layer
  link: new HttpLink({ uri: "/graphql" }),

  // Avoid defaultOptions if possible as they break TypeScript expectations.
  // Configure options per-query/mutation instead for better type safety.
  // defaultOptions: {
  //   watchQuery: { fetchPolicy: 'cache-and-network' },
  // },

  // DevTools are enabled by default in development
  // Only configure when enabling in production
  devtools: {
    enabled: true, // Only needed for production
  },

  // Custom name for this client instance
  clientAwareness: {
    name: "web-client",
    version: "1.0.0",
  },
});
```

## Important Considerations

1. **Choose Your Hook Strategy:** Decide if your application should be based on Suspense. If it is, use suspenseful hooks like `useSuspenseQuery` (see [Suspense Hooks guide](suspense-hooks.md)), otherwise use non-suspenseful hooks like `useQuery` (see [Queries guide](queries.md)).

2. **Client-Side Only:** This setup is for client-side apps without SSR. The Apollo Client instance is created once and reused throughout the application lifecycle.

3. **Authentication:** Prefer HttpOnly cookies with `credentials: "include"` in `HttpLink` options to avoid exposing tokens to JavaScript. If manual token management is necessary, use `SetContextLink` to dynamically add authentication headers from `localStorage` or other client-side storage.

4. **Environment Variables:** Store your GraphQL endpoint URL in environment variables for different environments (development, staging, production).

5. **Error Handling:** Always handle `loading` and `error` states when using `useQuery` or `useLazyQuery`. For Suspense-based hooks (`useSuspenseQuery`), React handles this through `<Suspense>` boundaries and error boundaries.

```

### references/integration-nextjs.md

```markdown
# Apollo Client Integration with Next.js App Router

This guide covers integrating Apollo Client in a Next.js application using the App Router architecture with support for both React Server Components (RSC) and Client Components.

## What is supported?

### React Server Components

Apollo Client provides a shared client instance across all server components for a single request, preventing duplicate GraphQL requests and optimizing server-side rendering.

### React Client Components

When using the `app` directory, client components are rendered both on the server (SSR) and in the browser. Apollo Client enables you to execute GraphQL queries on the server and use the results to hydrate your browser-side cache, delivering fully-rendered pages to users.

## Installation

Install Apollo Client and the Next.js integration package:

```bash
npm install @apollo/client@latest @apollo/client-integration-nextjs graphql rxjs
```

> **TypeScript users:** For type-safe GraphQL operations, see the [TypeScript Code Generation guide](typescript-codegen.md).

## Setup for React Server Components (RSC)

### Step 1: Create Apollo Client Configuration

Create an `ApolloClient.ts` file in your app directory:

```typescript
import { HttpLink } from "@apollo/client";
import { registerApolloClient, ApolloClient, InMemoryCache } from "@apollo/client-integration-nextjs";

export const { getClient, query, PreloadQuery } = registerApolloClient(() => {
  return new ApolloClient({
    cache: new InMemoryCache(),
    link: new HttpLink({
      // Use an absolute URL for SSR (relative URLs cannot be used in SSR)
      uri: "https://your-api.com/graphql",
      fetchOptions: {
        // Optional: Next.js-specific fetch options for caching and revalidation
        // See: https://nextjs.org/docs/app/api-reference/functions/fetch
      },
    }),
  });
});
```

### Step 2: Use in Server Components

You can now use the `getClient` function or the `query` shortcut in your server components:

```typescript
import { query } from "./ApolloClient";

async function UserProfile({ userId }: { userId: string }) {
  const { data } = await query({
    query: GET_USER,
    variables: { id: userId },
  });

  return <div>{data.user.name}</div>;
}
```

### Override Next.js Fetch Options

You can override Next.js-specific `fetch` options per query using `context.fetchOptions`:

```typescript
const { data } = await getClient().query({
  query: GET_USER,
  context: {
    fetchOptions: {
      next: { revalidate: 60 }, // Revalidate every 60 seconds
    },
  },
});
```

## Setup for Client Components (SSR and Browser)

### Step 1: Create Apollo Wrapper Component

Create `app/ApolloWrapper.tsx`:

```typescript
"use client";

import { HttpLink } from "@apollo/client";
import {
  ApolloNextAppProvider,
  ApolloClient,
  InMemoryCache,
} from "@apollo/client-integration-nextjs";

function makeClient() {
  const httpLink = new HttpLink({
    // Use an absolute URL for SSR
    uri: "https://your-api.com/graphql",
    fetchOptions: {
      // Optional: Next.js-specific fetch options
      // Note: This doesn't work with `export const dynamic = "force-static"`
    },
  });

  return new ApolloClient({
    cache: new InMemoryCache(),
    link: httpLink,
  });
}

export function ApolloWrapper({ children }: React.PropsWithChildren) {
  return (
    <ApolloNextAppProvider makeClient={makeClient}>
      {children}
    </ApolloNextAppProvider>
  );
}
```

### Step 2: Wrap Root Layout

Wrap your `RootLayout` in the `ApolloWrapper` component in `app/layout.tsx`:

```typescript
import { ApolloWrapper } from "./ApolloWrapper";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <ApolloWrapper>{children}</ApolloWrapper>
      </body>
    </html>
  );
}
```

> **Note:** This works even if your layout is a React Server Component. It ensures all Client Components share the same Apollo Client instance through `ApolloNextAppProvider`.

### Step 3: Use Apollo Client Hooks in Client Components

For optimal streaming SSR, use suspense-enabled hooks like `useSuspenseQuery` and `useFragment`:

```typescript
"use client";

import { useSuspenseQuery } from "@apollo/client/react";

export function UserProfile({ userId }: { userId: string }) {
  const { data } = useSuspenseQuery(GET_USER, {
    variables: { id: userId },
  });

  return <div>{data.user.name}</div>;
}
```

## Preloading Data from RSC to Client Components

You can preload data in React Server Components to populate the cache of your Client Components.

### Step 1: Use PreloadQuery in Server Components

```tsx
import { PreloadQuery } from "./ApolloClient";
import { Suspense } from "react";

export default async function Page() {
  return (
    <PreloadQuery query={GET_USER} variables={{ id: "1" }}>
      <Suspense fallback={<>Loading...</>}>
        <ClientChild />
      </Suspense>
    </PreloadQuery>
  );
}
```

### Step 2: Consume with useSuspenseQuery in Client Components

```tsx
"use client";

import { useSuspenseQuery } from "@apollo/client/react";

export function ClientChild() {
  const { data } = useSuspenseQuery(GET_USER, {
    variables: { id: "1" },
  });

  return <div>{data.user.name}</div>;
}
```

> **Important:** Data fetched this way should be considered client data and never referenced in Server Components. `PreloadQuery` prevents mixing server data and client data by creating a separate `ApolloClient` instance.

### Using with useReadQuery

For advanced use cases, you can use `PreloadQuery` with `useReadQuery` to avoid request waterfalls:

```tsx
<PreloadQuery query={GET_USER} variables={{ id: "1" }}>
  {(queryRef) => (
    <Suspense fallback={<>Loading...</>}>
      <ClientChild queryRef={queryRef} />
    </Suspense>
  )}
</PreloadQuery>
```

In your Client Component:

```tsx
"use client";

import { useQueryRefHandlers, useReadQuery, QueryRef } from "@apollo/client/react";

export function ClientChild({ queryRef }: { queryRef: QueryRef<TQueryData> }) {
  const { refetch } = useQueryRefHandlers(queryRef);
  const { data } = useReadQuery(queryRef);

  return <div>{data.user.name}</div>;
}
```

## Handling Multipart Responses (@defer) in SSR

When using the `@defer` directive, `useSuspenseQuery` will only suspend until the initial response is received. To handle deferred data properly, you have three strategies:

### Strategy 1: Use PreloadQuery with useReadQuery

`PreloadQuery` allows deferred data to be fully transported and streamed chunk-by-chunk.

### Strategy 2: Remove @defer Fragments

Use `RemoveMultipartDirectivesLink` to strip `@defer` directives from queries during SSR:

```typescript
import { RemoveMultipartDirectivesLink } from "@apollo/client-integration-nextjs";

new RemoveMultipartDirectivesLink({
  stripDefer: true, // Default: true
});
```

You can exclude specific fragments from stripping by labeling them:

```graphql
query myQuery {
  fastField
  ... @defer(label: "SsrDontStrip1") {
    slowField1
  }
}
```

### Strategy 3: Wait for Deferred Data

Use `AccumulateMultipartResponsesLink` to debounce the initial response:

```typescript
import { AccumulateMultipartResponsesLink } from "@apollo/client-integration-nextjs";

new AccumulateMultipartResponsesLink({
  cutoffDelay: 100, // Wait up to 100ms for incremental data
});
```

### Combined Approach: SSRMultipartLink

Combine both strategies with `SSRMultipartLink`:

```typescript
import { SSRMultipartLink } from "@apollo/client-integration-nextjs";

new SSRMultipartLink({
  stripDefer: true,
  cutoffDelay: 100,
});
```

## Testing

Reset singleton instances between tests using the `resetApolloClientSingletons` helper:

```typescript
import { resetApolloClientSingletons } from "@apollo/client-integration-nextjs";

afterEach(resetApolloClientSingletons);
```

## Debugging

Enable verbose logging in your `app/ApolloWrapper.tsx`:

```typescript
import { setLogVerbosity } from "@apollo/client";

setLogVerbosity("debug");
```

## Important Considerations

1. **Separate RSC and SSR Queries:** Avoid overlapping queries between RSC and SSR. RSC queries don't update in the browser, while SSR queries can update dynamically as the cache changes.

2. **Use Absolute URLs:** Always use absolute URLs in `HttpLink` for SSR, as relative URLs cannot be used in server-side rendering.

3. **Streaming SSR:** For optimal performance, use `useSuspenseQuery` and `useFragment` to take advantage of React 18's streaming SSR capabilities.

4. **Suspense Boundaries:** Place `Suspense` boundaries at meaningful places in your UI for the best user experience.

```

### references/integration-react-router.md

```markdown
# Apollo Client Integration with React Router Framework Mode

This guide covers integrating Apollo Client in a React Router 7 application with support for modern streaming SSR.

## Installation

Install Apollo Client and the React Router integration package:

```bash
npm install @apollo/client-integration-react-router @apollo/client graphql rxjs
```

> **TypeScript users:** For type-safe GraphQL operations, see the [TypeScript Code Generation guide](typescript-codegen.md).

## Setup

### Step 1: Create Apollo Configuration

Create an `app/apollo.ts` file that exports a `makeClient` function and an `apolloLoader`:

```typescript
import { HttpLink, InMemoryCache } from "@apollo/client";
import { createApolloLoaderHandler, ApolloClient } from "@apollo/client-integration-react-router";

// `request` will be available on the server during SSR or in loaders, but not in the browser
export const makeClient = (request?: Request) => {
  return new ApolloClient({
    cache: new InMemoryCache(),
    link: new HttpLink({ uri: "https://your-graphql-endpoint.com/graphql" }),
  });
};

export const apolloLoader = createApolloLoaderHandler(makeClient);
```

> **Important:** `ApolloClient` must be imported from `@apollo/client-integration-react-router`, not from `@apollo/client`.

### Step 2: Reveal Entry Files

Run the following command to create the entry files if they don't exist:

```bash
npx react-router reveal
```

This will create `app/entry.client.tsx` and `app/entry.server.tsx`.

### Step 3: Configure Client Entry

Adjust `app/entry.client.tsx` to wrap your app in `ApolloProvider`:

```typescript
import { makeClient } from "./apollo";
import { ApolloProvider } from "@apollo/client";
import { StrictMode, startTransition } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";

startTransition(() => {
  const client = makeClient();
  hydrateRoot(
    document,
    <StrictMode>
      <ApolloProvider client={client}>
        <HydratedRouter />
      </ApolloProvider>
    </StrictMode>
  );
});
```

### Step 4: Configure Server Entry

Adjust `app/entry.server.tsx` to wrap your app in `ApolloProvider` during SSR:

```typescript
import { makeClient } from "./apollo";
import { ApolloProvider } from "@apollo/client";
// ... other imports

export default function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  routerContext: EntryContext
) {
  return new Promise((resolve, reject) => {
    // ... existing code

    const client = makeClient(request);

    const { pipe, abort } = renderToPipeableStream(
      <ApolloProvider client={client}>
        <ServerRouter
          context={routerContext}
          url={request.url}
          abortDelay={ABORT_DELAY}
        />
      </ApolloProvider>,
      {
        [readyOption]() {
          shellRendered = true;
          // ... rest of the handler
        },
        // ... other options
      }
    );
  });
}
```

### Step 5: Add Hydration Helper

Add `<ApolloHydrationHelper>` to `app/root.tsx`:

```typescript
import { ApolloHydrationHelper } from "@apollo/client-integration-react-router";
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";

export function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        <ApolloHydrationHelper>{children}</ApolloHydrationHelper>
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

export default function App() {
  return <Outlet />;
}
```

## Usage

### Using apolloLoader with useReadQuery

You can now use the `apolloLoader` function to create Apollo-enabled loaders for your routes:

```typescript
import { gql } from "@apollo/client";
import { useReadQuery } from "@apollo/client/react";
import { useLoaderData } from "react-router";
import type { Route } from "./+types/my-route";
import type { TypedDocumentNode } from "@apollo/client";
import { apolloLoader } from "./apollo";

// TypedDocumentNode definition with types
const GET_USER: TypedDocumentNode<
  { user: { id: string; name: string; email: string } },
  { id: string }
> = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`;

export const loader = apolloLoader<Route.LoaderArgs>()(({ preloadQuery }) => {
  const userQueryRef = preloadQuery(GET_USER, {
    variables: { id: "1" },
  });

  return {
    userQueryRef,
  };
});

export default function UserPage() {
  const { userQueryRef } = useLoaderData<typeof loader>();
  const { data } = useReadQuery(userQueryRef);

  return (
    <div>
      <h1>{data.user.name}</h1>
      <p>{data.user.email}</p>
    </div>
  );
}
```

> **Important:** To provide better TypeScript support, `apolloLoader` is a method that you need to call twice: `apolloLoader<LoaderArgs>()(loader)`

### Multiple Queries in a Loader

You can preload multiple queries in a single loader:

```typescript
import { gql } from "@apollo/client";
import { useReadQuery } from "@apollo/client/react";
import { useLoaderData } from "react-router";
import type { Route } from "./+types/my-route";
import { apolloLoader } from "./apollo";

// TypedDocumentNode definitions omitted for brevity

export const loader = apolloLoader<Route.LoaderArgs>()(({ preloadQuery }) => {
  const userQueryRef = preloadQuery(GET_USER, {
    variables: { id: "1" },
  });

  const postsQueryRef = preloadQuery(GET_POSTS, {
    variables: { userId: "1" },
  });

  return {
    userQueryRef,
    postsQueryRef,
  };
});

export default function UserPage() {
  const { userQueryRef, postsQueryRef } = useLoaderData<typeof loader>();
  const { data: userData } = useReadQuery(userQueryRef);
  const { data: postsData } = useReadQuery(postsQueryRef);

  return (
    <div>
      <h1>{userData.user.name}</h1>
      <h2>Posts</h2>
      <ul>
        {postsData.posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}
```

## Important Considerations

1. **Import ApolloClient from Integration Package:** Always import `ApolloClient` from `@apollo/client-integration-react-router`, not from `@apollo/client`, to ensure proper SSR hydration.

2. **TypeScript Support:** The `apolloLoader` function requires double invocation for proper TypeScript type inference: `apolloLoader<LoaderArgs>()(loader)`.

3. **Request Context:** The `makeClient` function receives the `Request` object during SSR and in loaders, but not in the browser. Use this to set up auth headers or other request-specific configuration.

4. **Streaming SSR:** The integration fully supports React's streaming SSR capabilities. Place `Suspense` boundaries strategically for optimal user experience.

5. **Cache Hydration:** The `ApolloHydrationHelper` component ensures that data loaded on the server is properly hydrated on the client, preventing unnecessary refetches.

```

### references/integration-tanstack-start.md

```markdown
# Apollo Client Integration with TanStack Start

This guide covers integrating Apollo Client in a TanStack Start application with support for modern streaming SSR.

> **Note:** When using `npx create-tsrouter-app` to create a new TanStack Start application, you can choose Apollo Client in the setup wizard to have all of this configuration automatically set up for you.

## Installation

Install Apollo Client and the TanStack Start integration package:

```bash
npm install @apollo/client-integration-tanstack-start @apollo/client graphql rxjs
```

> **TypeScript users:** For type-safe GraphQL operations, see the [TypeScript Code Generation guide](typescript-codegen.md).

## Setup

### Step 1: Configure Root Route with Context

In your `routes/__root.tsx`, change from `createRootRoute` to `createRootRouteWithContext` to provide the right context type:

```typescript
import type { ApolloClientIntegration } from "@apollo/client-integration-tanstack-start";
import {
  createRootRouteWithContext,
  Outlet,
} from "@tanstack/react-router";

export const Route = createRootRouteWithContext<ApolloClientIntegration.RouterContext>()({
  component: RootComponent,
});

function RootComponent() {
  return (
    <html>
      <head>
        <meta charSet="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>My App</title>
      </head>
      <body>
        <Outlet />
      </body>
    </html>
  );
}
```

### Step 2: Set Up Apollo Client in Router

In your `router.tsx`, set up your Apollo Client instance and run `routerWithApolloClient`:

```typescript
import { routerWithApolloClient, ApolloClient, InMemoryCache } from "@apollo/client-integration-tanstack-start";
import { HttpLink } from "@apollo/client";
import { createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";

export function getRouter() {
  const apolloClient = new ApolloClient({
    cache: new InMemoryCache(),
    link: new HttpLink({ uri: "https://your-graphql-endpoint.com/graphql" }),
  });

  const router = createRouter({
    routeTree,
    context: {
      ...routerWithApolloClient.defaultContext,
    },
  });

  return routerWithApolloClient(router, apolloClient);
}
```

> **Important:** `ApolloClient` and `InMemoryCache` must be imported from `@apollo/client-integration-tanstack-start`, not from `@apollo/client`.

## Usage

### Option 1: Loader with preloadQuery and useReadQuery

Use the `preloadQuery` function in your route loader to preload data during navigation:

```typescript
import { gql } from "@apollo/client";
import { useReadQuery } from "@apollo/client/react";
import { createFileRoute } from "@tanstack/react-router";
import type { TypedDocumentNode } from "@apollo/client";

// TypedDocumentNode definition with types
const GET_USER: TypedDocumentNode<
  { user: { id: string; name: string; email: string } },
  { id: string }
> = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`;

export const Route = createFileRoute("/user/$userId")({
  component: RouteComponent,
  loader: ({ context: { preloadQuery }, params }) => {
    const queryRef = preloadQuery(GET_USER, {
      variables: { id: params.userId },
    });

    return {
      queryRef,
    };
  },
});

function RouteComponent() {
  const { queryRef } = Route.useLoaderData();
  const { data } = useReadQuery(queryRef);

  return (
    <div>
      <h1>{data.user.name}</h1>
      <p>{data.user.email}</p>
    </div>
  );
}
```

### Option 2: Direct useSuspenseQuery in Component

You can also use Apollo Client's suspenseful hooks directly in your component without a loader:

```typescript
import { gql, useSuspenseQuery } from "@apollo/client/react";
import { createFileRoute } from "@tanstack/react-router";
import type { TypedDocumentNode } from "@apollo/client";

// TypedDocumentNode definition with types
const GET_POSTS: TypedDocumentNode<{
  posts: Array<{ id: string; title: string; content: string }>;
}> = gql`
  query GetPosts {
    posts {
      id
      title
      content
    }
  }
`;

export const Route = createFileRoute("/posts")({
  component: RouteComponent,
});

function RouteComponent() {
  const { data } = useSuspenseQuery(GET_POSTS);

  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {data.posts.map((post) => (
          <li key={post.id}>
            <h2>{post.title}</h2>
            <p>{post.content}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}
```

### Multiple Queries in a Loader

You can preload multiple queries in a single loader:

```typescript
import { gql } from "@apollo/client";
import { useReadQuery } from "@apollo/client/react";
import { createFileRoute } from "@tanstack/react-router";

// TypedDocumentNode definitions omitted for brevity

export const Route = createFileRoute("/dashboard")({
  component: RouteComponent,
  loader: ({ context: { preloadQuery } }) => {
    const userQueryRef = preloadQuery(GET_USER, {
      variables: { id: "current" },
    });

    const statsQueryRef = preloadQuery(GET_STATS, {
      variables: { period: "month" },
    });

    return {
      userQueryRef,
      statsQueryRef,
    };
  },
});

function RouteComponent() {
  const { userQueryRef, statsQueryRef } = Route.useLoaderData();
  const { data: userData } = useReadQuery(userQueryRef);
  const { data: statsData } = useReadQuery(statsQueryRef);

  return (
    <div>
      <h1>Welcome, {userData.user.name}</h1>
      <div>
        <h2>Monthly Stats</h2>
        <p>Views: {statsData.stats.views}</p>
        <p>Clicks: {statsData.stats.clicks}</p>
      </div>
    </div>
  );
}
```

### Using useQueryRefHandlers for Refetching

When using `useReadQuery`, you can get refetch functionality from `useQueryRefHandlers`:

> **Important:** Always call `useQueryRefHandlers` before `useReadQuery`. These two hooks interact with the same `queryRef`, and calling them in the wrong order could cause subtle bugs.

```typescript
import { useReadQuery, useQueryRefHandlers, QueryRef } from "@apollo/client/react";

function UserComponent({ queryRef }: { queryRef: QueryRef<GetUserQuery> }) {
  const { refetch } = useQueryRefHandlers(queryRef);
  const { data } = useReadQuery(queryRef);

  return (
    <div>
      <h1>{data.user.name}</h1>
      <button onClick={() => refetch()}>Refresh</button>
    </div>
  );
}
```

## Important Considerations

1. **Import from Integration Package:** Always import `ApolloClient` and `InMemoryCache` from `@apollo/client-integration-tanstack-start`, not from `@apollo/client`, to ensure proper SSR hydration.

2. **Context Type:** Use `createRootRouteWithContext<ApolloClientIntegration.RouterContext>()` to provide proper TypeScript types for the `preloadQuery` function in loaders.

3. **Loader vs Component Queries:**
   - Use `preloadQuery` in loaders when you want to start fetching data before the component renders
   - Use `useSuspenseQuery` directly in components for simpler cases or when data fetching can wait until render

4. **Streaming SSR:** The integration fully supports React's streaming SSR capabilities. Place `Suspense` boundaries strategically for optimal user experience.

5. **Cache Management:** The Apollo Client instance is shared across all routes, so cache updates from one route will be reflected in all routes that use the same data.

6. **Authentication:** Use Apollo Client's `SetContextLink` for dynamic auth tokens.

## Advanced Configuration

### Adding Authentication

For authentication in TanStack Start with SSR support, you need to handle both server and client environments differently. Use `createIsomorphicFn` to provide environment-specific implementations:

```typescript
import { ApolloClient, InMemoryCache, routerWithApolloClient } from "@apollo/client-integration-tanstack-start";
import { ApolloLink, HttpLink } from "@apollo/client";
import { SetContextLink } from "@apollo/client/link/context";
import { createIsomorphicFn } from "@tanstack/react-start";
import { createRouter } from "@tanstack/react-router";
import { getSession, getCookie } from "@tanstack/react-start/server";
import { routeTree } from "./routeTree.gen";

// Create isomorphic link that uses different implementations per environment
const createAuthLink = createIsomorphicFn()
  .server(() => {
    // Server-only: Can access server-side functions like `getCookies`, `getCookie`, `getSession`, etc. exported from `"@tanstack/react-start/server"`
    return new SetContextLink(async (prevContext) => {
      return {
        headers: {
          ...prevContext.headers,
          authorization: getCookie("Authorization"),
        },
      };
    });
  })
  .client(() => {
    // Client-only: Can access `localStorage` or other browser APIs
    return new SetContextLink((prevContext) => {
      return {
        headers: {
          ...prevContext.headers,
          authorization: localStorage.getItem("authToken") ?? "",
        },
      };
    });
  });

export function getRouter() {
  const httpLink = new HttpLink({
    uri: "https://your-graphql-endpoint.com/graphql",
  });

  const apolloClient = new ApolloClient({
    cache: new InMemoryCache(),
    link: ApolloLink.from([createAuthLink(), httpLink]),
  });

  const router = createRouter({
    routeTree,
    context: {
      ...routerWithApolloClient.defaultContext,
    },
  });

  return routerWithApolloClient(router, apolloClient);
}
```

> **Important:** The `getRouter` function is called both on the server and client, so it must not contain environment-specific code. Use `createIsomorphicFn` to provide different implementations:
>
> - **Server:** Can access server-only functions like `getSession`, `getCookies`, `getCookie` from `@tanstack/react-start/server` to access authentication information in request or session data
> - **Client:** Can use `localStorage` or other browser APIs to access auth tokens (if setting `credentials: "include"` is sufficient, try to prefer that over manually setting auth headers client-side)
>
> This ensures your authentication works correctly in both SSR and browser contexts.

### Custom Cache Configuration

```typescript
import { ApolloClient, InMemoryCache } from "@apollo/client-integration-tanstack-start";
import { HttpLink } from "@apollo/client";
import { createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
import { routerWithApolloClient } from "@apollo/client-integration-tanstack-start";

export function getRouter() {
  const apolloClient = new ApolloClient({
    cache: new InMemoryCache({
      typePolicies: {
        Query: {
          fields: {
            posts: {
              merge(existing = [], incoming) {
                return [...existing, ...incoming];
              },
            },
          },
        },
      },
    }),
    link: new HttpLink({ uri: "https://your-graphql-endpoint.com/graphql" }),
  });

  const router = createRouter({
    routeTree,
    context: {
      ...routerWithApolloClient.defaultContext,
    },
  });

  return routerWithApolloClient(router, apolloClient);
}
```

```

### references/typescript-codegen.md

```markdown
# TypeScript Code Generation

This guide covers setting up GraphQL Code Generator for type-safe Apollo Client usage with TypeScript.

## Installation

```bash
npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typed-document-node
```

## Configuration

Create a `codegen.ts` file in your project root:

```typescript
// codegen.ts
import { CodegenConfig } from "@graphql-codegen/cli";

const config: CodegenConfig = {
  overwrite: true,
  schema: "<URL_OF_YOUR_GRAPHQL_API>",
  // This assumes that all your source files are in a top-level `src/` directory - you might need to adjust this to your file structure
  documents: ["src/**/*.{ts,tsx}"],
  // Don't exit with non-zero status when there are no documents
  ignoreNoDocuments: true,
  generates: {
    // Use a path that works the best for the structure of your application
    "./src/types/__generated__/graphql.ts": {
      plugins: ["typescript", "typescript-operations", "typed-document-node"],
      config: {
        avoidOptionals: {
          // Use `null` for nullable fields instead of optionals
          field: true,
          // Allow nullable input fields to remain unspecified
          inputValue: false,
        },
        // Use `unknown` instead of `any` for unconfigured scalars
        defaultScalarType: "unknown",
        // Apollo Client always includes `__typename` fields
        nonOptionalTypename: true,
        // Apollo Client doesn't add the `__typename` field to root types so
        // don't generate a type for the `__typename` for root operation types.
        skipTypeNameForRoot: true,
      },
    },
  },
};

export default config;
```

## Enable Data Masking

To enable data masking with GraphQL Code Generator, create a type declaration file to inform Apollo Client about the generated types:

```typescript
// apollo-client.d.ts
import { GraphQLCodegenDataMasking } from "@apollo/client/masking";

declare module "@apollo/client" {
  export interface TypeOverrides extends GraphQLCodegenDataMasking.TypeOverrides {}
}
```

## Running Code Generation

Add a script to your `package.json`:

```json
{
  "scripts": {
    "codegen": "graphql-codegen"
  }
}
```

Run code generation:

```bash
npm run codegen
```

## Usage with Apollo Client

The typed-document-node plugin generates `TypedDocumentNode` types that Apollo Client hooks automatically infer.

### Defining Operations

Define your operations inline with the `if (false)` pattern. This allows GraphQL Code Generator to detect and extract operations without executing the code at runtime (bundlers omit this dead code during minification):

```typescript
import { gql } from "@apollo/client";

// This query will never be consumed in runtime code, so it is wrapped in `if (false)` so the bundler can omit it when bundling.
if (false) {
  gql`
    query GetUser($id: ID!) {
      user(id: $id) {
        id
        name
        email
      }
    }
  `;
}
```

### Using Generated Types

After running `npm run codegen`, import the generated `TypedDocumentNode`:

```typescript
import { useQuery } from "@apollo/client/react";
import { GetUserDocument } from "./queries.generated";

function UserProfile({ userId }: { userId: string }) {
  // Types are automatically inferred from GetUserDocument
  const { data } = useQuery(GetUserDocument, {
    variables: { id: userId },
  });

  return <div>{data.user.name}</div>;
}
```

## Important Notes

- The typed-document-node plugin might have a bundle size tradeoff but can prevent inconsistencies and is best suited for usage with LLMs, so it is recommended for most applications.
- See the [GraphQL Code Generator documentation](https://www.apollographql.com/docs/react/development-testing/graphql-codegen#recommended-starter-configuration) for other recommended configuration patterns if required.
- Apollo Client hooks automatically infer types from `TypedDocumentNode` - never use manual generics like `useQuery<QueryType, VariablesType>()`.

```

### references/queries.md

```markdown
# Queries Reference

> **Note**: In most applications, there should only be one use of a query hook per page. Use fragment-reading hooks (`useFragment`, `useSuspenseFragment`) with component-colocated fragments and data masking for the rest of the page components.

## Table of Contents

- [useQuery Hook](#usequery-hook)
- [Query Variables](#query-variables)
- [Loading and Error States](#loading-and-error-states)
- [useLazyQuery](#uselazyquery)
- [Polling and Refetching](#polling-and-refetching)
- [Fetch Policies](#fetch-policies)
- [Conditional Queries](#conditional-queries)

## useQuery Hook

The `useQuery` hook is the primary way to fetch data in Apollo Client in non-suspenseful applications. It returns loading and error states that must be handled.

> **Note**: In suspenseful applications, use `useSuspenseQuery` or `useBackgroundQuery` instead. See the [Suspense Hooks reference](suspense-hooks.md) for more details.

### Basic Usage

```tsx
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";

const GET_DOGS = gql`
  query GetDogs {
    dogs {
      id
      breed
      displayImage
    }
  }
`;

function Dogs() {
  const { loading, error, data } = useQuery(GET_DOGS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data.dogs.map((dog) => (
        <li key={dog.id}>{dog.breed}</li>
      ))}
    </ul>
  );
}
```

### Return Object

```typescript
const {
  data, // Query result data
  loading, // True during initial load
  error, // ApolloError if request failed
  networkStatus, // Detailed network state (1-8)
  dataState, // For TypeScript type narrowing (AC 4.x)
  refetch, // Function to re-execute query
  fetchMore, // Function for pagination
  startPolling, // Start polling at interval
  stopPolling, // Stop polling
  subscribeToMore, // Add subscription to query
  updateQuery, // Manually update query result
  client, // Apollo Client instance
  called, // True if query has been executed
  previousData, // Previous data (useful during loading)
} = useQuery(QUERY);
```

## Query Variables

### Basic Variables

```tsx
const GET_DOG = gql`
  query GetDog($breed: String!) {
    dog(breed: $breed) {
      id
      displayImage
    }
  }
`;

function DogPhoto({ breed }: { breed: string }) {
  const { loading, error, data } = useQuery(GET_DOG, {
    variables: { breed },
  });

  if (loading) return null;
  if (error) return <p>Error: {error.message}</p>;

  return <img src={data.dog.displayImage} alt={breed} />;
}
```

### TypeScript Types

Use `TypedDocumentNode` instead of generic type parameters for better type safety:

```typescript
import { gql, TypedDocumentNode } from "@apollo/client";
import { useQuery } from "@apollo/client/react";

interface GetDogData {
  dog: {
    id: string;
    displayImage: string;
  };
}

interface GetDogVariables {
  breed: string;
}

const GET_DOG: TypedDocumentNode<GetDogData, GetDogVariables> = gql`
  query GetDog($breed: String!) {
    dog(breed: $breed) {
      id
      displayImage
    }
  }
`;

const { data } = useQuery(GET_DOG, {
  variables: { breed: "bulldog" },
});

// data?.dog is fully typed
```

### Dynamic Variables

```tsx
function DogSelector() {
  const [breed, setBreed] = useState("bulldog");

  // Query automatically re-runs when breed changes
  const { data } = useQuery(GET_DOG, {
    variables: { breed },
  });

  return (
    <select value={breed} onChange={(e) => setBreed(e.target.value)}>
      <option value="bulldog">Bulldog</option>
      <option value="poodle">Poodle</option>
    </select>
  );
}
```

## Loading and Error States

### Using Previous Data

```tsx
function UserProfile({ userId }: { userId: string }) {
  const { loading, data, previousData } = useQuery(GET_USER, {
    variables: { id: userId },
  });

  // Show previous data while loading new data
  const displayData = data ?? previousData;

  return (
    <div>
      {loading && <LoadingSpinner />}
      {displayData && <UserCard user={displayData.user} />}
    </div>
  );
}
```

### Network Status

```tsx
import { NetworkStatus } from "@apollo/client";

function Dogs() {
  const { loading, error, data, networkStatus, refetch } = useQuery(GET_DOGS, {
    notifyOnNetworkStatusChange: true,
  });

  if (networkStatus === NetworkStatus.refetch) {
    return <p>Refetching...</p>;
  }

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <>
      <button onClick={() => refetch()}>Refresh</button>
      <ul>
        {data.dogs.map((dog) => (
          <li key={dog.id}>{dog.breed}</li>
        ))}
      </ul>
    </>
  );
}
```

## useLazyQuery

Use `useLazyQuery` when you want to execute a query in response to a user-triggered event (like a button click) rather than on component mount.

**Important**: `useLazyQuery` doesn't guarantee a network request - it only sets variables. If data is already in the cache, this isn't a "refetch". Only use `useLazyQuery` if you consume the second tuple value (loading, data, error states) to synchronize cache data with the component. If you only need the promise, use `client.query` directly instead.

### Basic Usage

```tsx
import { gql } from "@apollo/client";
import { useLazyQuery } from "@apollo/client/react";

const GET_DOG_PHOTO = gql`
  query GetDogPhoto($breed: String!) {
    dog(breed: $breed) {
      id
      displayImage
    }
  }
`;

function DelayedQuery() {
  const [getDog, { loading, error, data, called }] = useLazyQuery(GET_DOG_PHOTO);

  if (called && loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      {data?.dog && <img src={data.dog.displayImage} />}
      <button onClick={() => getDog({ variables: { breed: "bulldog" } })}>Get Bulldog Photo</button>
    </div>
  );
}
```

### When to Use client.query Instead

If you only need the promise result and don't consume the loading/error/data states from the hook, use `client.query` instead:

```tsx
import { useApolloClient } from "@apollo/client/react";

function SearchDogs() {
  const client = useApolloClient();
  const [search, setSearch] = useState("");

  const handleSearch = async () => {
    try {
      const { data } = await client.query({
        query: SEARCH_DOGS,
        variables: { query: search },
      });
      console.log("Found dogs:", data.searchDogs);
    } catch (error) {
      console.error("Search failed:", error);
    }
  };

  return (
    <div>
      <input value={search} onChange={(e) => setSearch(e.target.value)} />
      <button onClick={handleSearch}>Search</button>
    </div>
  );
}
```

## Polling and Refetching

### Polling

```tsx
function LiveFeed() {
  const { data, startPolling, stopPolling } = useQuery(GET_FEED, {
    pollInterval: 5000, // Poll every 5 seconds
  });

  // Or control polling dynamically
  useEffect(() => {
    startPolling(5000);
    return () => stopPolling();
  }, [startPolling, stopPolling]);

  return <Feed items={data?.feed} />;
}
```

### Manual Refetching

```tsx
function DogList() {
  const { data, refetch } = useQuery(GET_DOGS);

  return (
    <div>
      <button onClick={() => refetch()}>Refresh</button>
      <button onClick={() => refetch({ breed: "poodle" })}>Refetch Poodles</button>
      <ul>
        {data?.dogs.map((dog) => (
          <li key={dog.id}>{dog.breed}</li>
        ))}
      </ul>
    </div>
  );
}
```

## Fetch Policies

Control how the query interacts with the cache.

| Policy              | Description                                                |
| ------------------- | ---------------------------------------------------------- |
| `cache-first`       | Return cached data if available, otherwise fetch (default) |
| `cache-only`        | Only return cached data, never fetch                       |
| `cache-and-network` | Return cached data immediately, then fetch and update      |
| `network-only`      | Always fetch, update cache, ignore cached data             |
| `no-cache`          | Always fetch, never read or write cache                    |
| `standby`           | Same as cache-first but doesn't auto-update                |

### Usage Examples

```tsx
// Real-time data - always fetch
const { data } = useQuery(GET_NOTIFICATIONS, {
  fetchPolicy: "network-only",
});

// Static data - prefer cache
const { data } = useQuery(GET_CATEGORIES, {
  fetchPolicy: "cache-first",
});

// Show cached data while fetching fresh data
const { data, loading } = useQuery(GET_POSTS, {
  fetchPolicy: "cache-and-network",
});

// Fetch once, then use cache
const { data } = useQuery(GET_USER_PROFILE, {
  fetchPolicy: "network-only",
  nextFetchPolicy: "cache-first",
});
```

### nextFetchPolicy

```tsx
// First request: network-only
// Subsequent requests: cache-first
const { data } = useQuery(GET_POSTS, {
  fetchPolicy: "network-only",
  nextFetchPolicy: "cache-first",
});

// Or use a function for more control
const { data } = useQuery(GET_POSTS, {
  fetchPolicy: "network-only",
  nextFetchPolicy: (currentFetchPolicy, { reason, observable }) => {
    if (reason === "after-fetch") {
      return "cache-first";
    }
    return currentFetchPolicy;
  },
});
```

## Conditional Queries

### Using skipToken (Recommended)

Use `skipToken` to conditionally skip queries without TypeScript issues:

```tsx
import { skipToken } from "@apollo/client";

function UserProfile({ userId }: { userId: string | null }) {
  const { data } = useQuery(
    GET_USER,
    !userId
      ? skipToken
      : {
          variables: { id: userId },
        },
  );

  return userId ? <Profile user={data?.user} /> : <p>Select a user</p>;
}
```

### Skip Option (Alternative)

```tsx
function UserProfile({ userId }: { userId: string | null }) {
  const { data } = useQuery(GET_USER, {
    variables: { id: userId! },
    skip: !userId, // Don't execute if no userId
  });

  return userId ? <Profile user={data?.user} /> : <p>Select a user</p>;
}
```

> **Note**: Using `skipToken` is preferred over `skip` as it avoids TypeScript issues with required variables and the non-null assertion operator.

### SSR Skip

```tsx
// Skip during server-side rendering
const { data } = useQuery(GET_USER_LOCATION, {
  skip: typeof window === "undefined",
  ssr: false,
});
```

```

### references/suspense-hooks.md

```markdown
# Suspense Hooks Reference

> **Note**: Suspense hooks are the recommended approach for data fetching in modern React applications (React 18+). They provide cleaner code, better loading state handling, and enable streaming SSR.

## Table of Contents

- [useSuspenseQuery Hook](#usesuspensequery-hook)
- [useBackgroundQuery and useReadQuery](#usebackgroundquery-and-usereadquery)
- [useLoadableQuery](#useloadablequery)
- [createQueryPreloader](#createquerypreloader)
- [useQueryRefHandlers](#usequeryrefhandlers)
- [Distinguishing Queries with queryKey](#distinguishing-queries-with-querykey)
- [Suspense Boundaries and Error Handling](#suspense-boundaries-and-error-handling)
- [Transitions](#transitions)
- [Avoiding Request Waterfalls](#avoiding-request-waterfalls)
- [Fetch Policies](#fetch-policies)
- [Streaming SSR or React Server Components](#streaming-ssr-or-react-server-components)
- [Conditional Queries](#conditional-queries)

## useSuspenseQuery Hook

The `useSuspenseQuery` hook is the Suspense-ready replacement for `useQuery`. It initiates a network request and causes the component calling it to suspend while the request is made. Unlike `useQuery`, it does not return `loading` states—these are handled by React's Suspense boundaries and error boundaries.

### Basic Usage

```tsx
import { Suspense } from "react";
import { useSuspenseQuery } from "@apollo/client/react";
import { GET_DOG } from "./queries.generated";

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Dog id="3" />
    </Suspense>
  );
}

function Dog({ id }: { id: string }) {
  const { data } = useSuspenseQuery(GET_DOG, {
    variables: { id },
  });

  // data is always defined when this component renders
  return <div>Name: {data.dog.name}</div>;
}
```

### Return Object

```typescript
const {
  data, // Query result data
  dataState, // With default options: "complete" | "streaming"
  // With returnPartialData: also "partial"
  // With errorPolicy "all" or "ignore": also "empty"
  error, // ApolloError (only when errorPolicy is "all" or "ignore")
  networkStatus, // NetworkStatus.ready, NetworkStatus.loading, etc.
  client, // Apollo Client instance
  refetch, // Function to re-execute query
  fetchMore, // Function for pagination
} = useSuspenseQuery(QUERY, options);
```

### Key Differences from useQuery

- **No `loading` boolean**: Component suspends instead of returning `loading: true`
- **Error handling**: With default `errorPolicy` (`none`), errors are thrown and caught by error boundaries. With `errorPolicy: "all"` or `"ignore"`, the `error` property is returned and `data` may be `undefined`.
- **`data` availability**: With default `errorPolicy` (`none`), `data` is guaranteed to be present when the component renders. With `errorPolicy: "all"` or `"ignore"`, when `dataState` is `empty`, `data` may be `undefined`.
- **Suspense boundaries**: Must wrap component with `<Suspense>` to handle loading state

### Changing Variables

When variables change, `useSuspenseQuery` automatically re-runs the query. If the data is not in the cache, the component suspends again.

```tsx
import { useState } from "react";
import { GET_DOGS } from "./queries.generated";

function DogSelector() {
  const { data } = useSuspenseQuery(GET_DOGS);
  const [selectedDog, setSelectedDog] = useState(data.dogs[0].id);

  return (
    <>
      <select value={selectedDog} onChange={(e) => setSelectedDog(e.target.value)}>
        {data.dogs.map((dog) => (
          <option key={dog.id} value={dog.id}>
            {dog.name}
          </option>
        ))}
      </select>
      <Suspense fallback={<div>Loading...</div>}>
        <Dog id={selectedDog} />
      </Suspense>
    </>
  );
}

function Dog({ id }: { id: string }) {
  const { data } = useSuspenseQuery(GET_DOG, {
    variables: { id },
  });

  return (
    <>
      <div>Name: {data.dog.name}</div>
      <div>Breed: {data.dog.breed}</div>
    </>
  );
}
```

### Rendering Partial Data

Use `returnPartialData` to render immediately with partial cache data instead of suspending. The component will still suspend if there is no data in the cache.

```tsx
function Dog({ id }: { id: string }) {
  const { data } = useSuspenseQuery(GET_DOG, {
    variables: { id },
    returnPartialData: true,
  });

  return (
    <>
      <div>Name: {data.dog?.name ?? "Unknown"}</div>
      {data.dog?.breed && <div>Breed: {data.dog.breed}</div>}
    </>
  );
}
```

## useBackgroundQuery and useReadQuery

Use `useBackgroundQuery` with `useReadQuery` to avoid request waterfalls by starting a query in a parent component and reading the result in a child component. This pattern enables the parent to start fetching data before the child component renders.

### Basic Usage

```tsx
import { Suspense } from "react";
import { useBackgroundQuery, useReadQuery } from "@apollo/client/react";

function Parent() {
  // Start fetching immediately
  const [queryRef] = useBackgroundQuery(GET_DOG, {
    variables: { id: "3" },
  });

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Child queryRef={queryRef} />
    </Suspense>
  );
}

function Child({ queryRef }: { queryRef: QueryRef<DogData> }) {
  // Read the query result
  const { data } = useReadQuery(queryRef);

  return <div>Name: {data.dog.name}</div>;
}
```

### When to Use

- **Avoiding waterfalls**: Start fetching data in a parent (preferably above a suspense boundary) before child components render
- **Preloading data**: Begin fetching before the component that needs the data is ready
- **Parallel queries**: Start multiple queries at once in a parent component

### Return Values

`useBackgroundQuery` returns a tuple:

```typescript
const [
  queryRef, // QueryRef to pass to useReadQuery
  { refetch, fetchMore, subscribeToMore }, // Helper functions
] = useBackgroundQuery(QUERY, options);
```

`useReadQuery` returns the query result:

```typescript
const {
  data, // Query result data (always defined)
  dataState, // "complete" | "streaming" | "partial" | "empty"
  error, // ApolloError (if errorPolicy allows)
  networkStatus, // Detailed network state (1-8)
} = useReadQuery(queryRef);
```

## useLoadableQuery

Use `useLoadableQuery` to imperatively load a query in response to a user interaction (like a button click) instead of on component mount.

### Basic Usage

```tsx
import { Suspense } from "react";
import { useLoadableQuery, useReadQuery } from "@apollo/client/react";
import { GET_GREETING } from "./queries.generated";

function App() {
  const [loadGreeting, queryRef] = useLoadableQuery(GET_GREETING);

  return (
    <>
      <button onClick={() => loadGreeting({ variables: { language: "english" } })}>Load Greeting</button>
      <Suspense fallback={<div>Loading...</div>}>{queryRef && <Greeting queryRef={queryRef} />}</Suspense>
    </>
  );
}

function Greeting({ queryRef }: { queryRef: QueryRef<GreetingData> }) {
  const { data } = useReadQuery(queryRef);

  return <div>{data.greeting.message}</div>;
}
```

### Return Values

```typescript
const [
  loadQuery, // Function to load the query
  queryRef, // QueryRef (null until loadQuery is called)
  { refetch, fetchMore, subscribeToMore, reset }, // Helper functions
] = useLoadableQuery(QUERY, options);
```

### When to Use

- **User-triggered fetching**: Load data in response to user actions
- **Lazy loading**: Defer data fetching until it's actually needed
- **Progressive disclosure**: Load data for UI elements that may not be initially visible

## createQueryPreloader

The `createQueryPreloader` function creates a `preloadQuery` function that can be used to initiate queries outside of React components. This is useful for preloading data before a component renders, such as in route loaders or event handlers.

### Basic Usage

```tsx
import { ApolloClient, InMemoryCache } from "@apollo/client";
import { createQueryPreloader } from "@apollo/client/react";

const client = new ApolloClient({
  uri: "https://your-graphql-endpoint.com/graphql",
  cache: new InMemoryCache(),
});

// Create a preload function
export const preloadQuery = createQueryPreloader(client);
```

### Using preloadQuery with Route Loaders

> **Note**: This example applies to React Router in non-framework mode. For React Router framework mode, see [setup-react-router.md](./setup-react-router.md).

Use the preload function with React Router's `loader` function to begin loading data during route transitions:

```tsx
import { preloadQuery } from "@/lib/apollo-client";
import { GET_DOG } from "./queries.generated";

// React Router loader function
export async function loader({ params }: { params: { id: string } }) {
  return preloadQuery({
    query: GET_DOG,
    variables: { id: params.id },
  });
}

// Route component
export default function DogRoute() {
  const queryRef = useLoaderData();

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <DogDetails queryRef={queryRef} />
    </Suspense>
  );
}

function DogDetails({ queryRef }: { queryRef: QueryRef<DogData> }) {
  const { data } = useReadQuery(queryRef);

  return (
    <div>
      <h1>{data.dog.name}</h1>
      <p>Breed: {data.dog.breed}</p>
    </div>
  );
}
```

### Preventing Route Transitions Until Query Loads

Use the `toPromise()` method to prevent route transitions until the query finishes loading:

```tsx
export async function loader({ params }: { params: { id: string } }) {
  const queryRef = preloadQuery({
    query: GET_DOG,
    variables: { id: params.id },
  });

  // Wait for the query to complete before transitioning
  return queryRef.toPromise();
}
```

When `toPromise()` is used, the route transition waits for the query to complete, and the data renders immediately without showing a loading fallback.

> **Note**: `toPromise()` resolves with the `queryRef` itself (not the data) to encourage using `useReadQuery` for cache updates. If you need raw query data in your loader, use `client.query()` directly.

### With Next.js Server Components

> **Note**: For Next.js App Router, use the `PreloadQuery` component from `@apollo/client-integration-nextjs` instead. See [setup-nextjs.md](./setup-nextjs.md) for details.

## useQueryRefHandlers

The `useQueryRefHandlers` hook provides access to `refetch` and `fetchMore` functions for queries initiated with `preloadQuery`, `useBackgroundQuery`, or `useLoadableQuery`. This is useful when you need to refetch or paginate data in components where the `queryRef` is passed through.

> **Important:** Always call `useQueryRefHandlers` before `useReadQuery`. These two hooks interact with the same `queryRef`, and calling them in the wrong order could cause subtle bugs.

### Basic Usage

```tsx
import { useQueryRefHandlers } from "@apollo/client/react";

function Breeds({ queryRef }: { queryRef: QueryRef<BreedsData> }) {
  const { refetch } = useQueryRefHandlers(queryRef);
  const { data } = useReadQuery(queryRef);
  const [isPending, startTransition] = useTransition();

  return (
    <div>
      <button
        disabled={isPending}
        onClick={() => {
          startTransition(() => {
            refetch();
          });
        }}
      >
        {isPending ? "Refetching..." : "Refetch breeds"}
      </button>
      <ul>
        {data.breeds.map((breed) => (
          <li key={breed.id}>{breed.name}</li>
        ))}
      </ul>
    </div>
  );
}
```

### With Pagination

Use `fetchMore` to implement pagination:

```tsx
function Posts({ queryRef }: { queryRef: QueryRef<PostsData> }) {
  const { fetchMore } = useQueryRefHandlers(queryRef);
  const { data } = useReadQuery(queryRef);
  const [isPending, startTransition] = useTransition();

  return (
    <div>
      <ul>
        {data.posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
      <button
        disabled={isPending}
        onClick={() => {
          startTransition(() => {
            fetchMore({
              variables: {
                offset: data.posts.length,
              },
            });
          });
        }}
      >
        {isPending ? "Loading..." : "Load more"}
      </button>
    </div>
  );
}
```

### When to Use

- **Preloaded queries**: Access refetch/fetchMore for queries initiated with `preloadQuery`
- **Background queries**: Use in child components receiving `queryRef` from `useBackgroundQuery`
- **Loadable queries**: Refetch or paginate queries initiated with `useLoadableQuery`
- **React transitions**: Integrate with transitions to avoid showing loading fallbacks during refetches

## Distinguishing Queries with queryKey

Apollo Client uses the combination of `query` and `variables` to uniquely identify each query. When multiple components use the same `query` and `variables`, they share the same identity and suspend at the same time, regardless of which component initiates the request.

Use the `queryKey` option to ensure each hook has a unique identity:

```tsx
function UserProfile() {
  // First query with unique key
  const { data: userData } = useSuspenseQuery(GET_USER, {
    variables: { id: "1" },
    queryKey: ["user-profile"],
  });

  // Second query with same query and variables but different key
  const { data: userPreview } = useSuspenseQuery(GET_USER, {
    variables: { id: "1" },
    queryKey: ["user-preview"],
  });

  return (
    <div>
      <UserCard user={userData.user} />
      <UserSidebar user={userPreview.user} />
    </div>
  );
}
```

### When to Use

- **Multiple instances**: When rendering multiple components that use the same query and variables
- **Preventing shared suspension**: When you want independent control over when each query suspends
- **Separate cache entries**: When you need to maintain separate cache states for the same query

> **Note**: Each item in the `queryKey` array must be a stable identifier to prevent infinite fetches.

## Suspense Boundaries and Error Handling

### Suspense Boundaries

Wrap components that use Suspense hooks with `<Suspense>` boundaries to handle loading states. Place boundaries strategically to control the granularity of loading indicators.

```tsx
function App() {
  return (
    <>
      {/* Top-level loading for entire page */}
      <Suspense fallback={<PageSpinner />}>
        <Header />
        <Content />
      </Suspense>
    </>
  );
}

function Content() {
  return (
    <>
      <MainSection />
      {/* Granular loading for sidebar */}
      <Suspense fallback={<SidebarSkeleton />}>
        <Sidebar />
      </Suspense>
    </>
  );
}
```

### Error Boundaries

Suspense hooks throw errors to React error boundaries instead of returning them. Use error boundaries to handle GraphQL errors.

```tsx
import { ErrorBoundary } from "react-error-boundary";

function App() {
  return (
    <ErrorBoundary
      fallback={({ error }) => (
        <div>
          <h2>Something went wrong</h2>
          <p>{error.message}</p>
        </div>
      )}
    >
      <Suspense fallback={<div>Loading...</div>}>
        <Dog id="3" />
      </Suspense>
    </ErrorBoundary>
  );
}
```

### Custom Error Policies

Use `errorPolicy` to control how errors are handled:

```tsx
function Dog({ id }: { id: string }) {
  const { data, error } = useSuspenseQuery(GET_DOG, {
    variables: { id },
    errorPolicy: "all", // Return both data and errors
  });

  return (
    <>
      <div>Name: {data?.dog?.name ?? "Unknown"}</div>
      {error && <div>Warning: {error.message}</div>}
    </>
  );
}
```

## Transitions

Use React transitions to avoid showing loading UI when updating state. Transitions keep the previous UI visible while new data is fetching.

### Using startTransition

```tsx
import { useState, Suspense, startTransition } from "react";

function DogSelector() {
  const { data } = useSuspenseQuery(GET_DOGS);
  const [selectedDog, setSelectedDog] = useState(data.dogs[0].id);

  return (
    <>
      <select
        value={selectedDog}
        onChange={(e) => {
          // Wrap state update in startTransition
          startTransition(() => {
            setSelectedDog(e.target.value);
          });
        }}
      >
        {data.dogs.map((dog) => (
          <option key={dog.id} value={dog.id}>
            {dog.name}
          </option>
        ))}
      </select>
      <Suspense fallback={<div>Loading...</div>}>
        <Dog id={selectedDog} />
      </Suspense>
    </>
  );
}
```

### Using useTransition

Use `useTransition` to get an `isPending` flag for visual feedback during transitions.

```tsx
import { useState, Suspense, useTransition } from "react";

function DogSelector() {
  const [isPending, startTransition] = useTransition();
  const { data } = useSuspenseQuery(GET_DOGS);
  const [selectedDog, setSelectedDog] = useState(data.dogs[0].id);

  return (
    <>
      <select
        style={{ opacity: isPending ? 0.5 : 1 }}
        value={selectedDog}
        onChange={(e) => {
          startTransition(() => {
            setSelectedDog(e.target.value);
          });
        }}
      >
        {data.dogs.map((dog) => (
          <option key={dog.id} value={dog.id}>
            {dog.name}
          </option>
        ))}
      </select>
      <Suspense fallback={<div>Loading...</div>}>
        <Dog id={selectedDog} />
      </Suspense>
    </>
  );
}
```

## Avoiding Request Waterfalls

Request waterfalls occur when a child component waits for the parent to finish rendering before it can start fetching its own data. Use `useBackgroundQuery` to start fetching child data earlier in the component tree.

> **Note**: When one query depends on the result of another query (e.g., the child query needs an ID from the parent query), the waterfall is unavoidable. The best solution is to restructure your schema to fetch all needed data in a single nested query.

### Example: Independent Queries

When queries don't depend on each other, use `useBackgroundQuery` to start them in parallel:

```tsx
const GET_USER = gql`
  query GetUser($id: String!) {
    user(id: $id) {
      id
      name
    }
  }
`;

const GET_POSTS = gql`
  query GetPosts {
    posts {
      id
      title
    }
  }
`;

function Parent() {
  // Both queries start immediately - no waterfall
  const [userRef] = useBackgroundQuery(GET_USER, {
    variables: { id: "1" },
  });

  const [postsRef] = useBackgroundQuery(GET_POSTS);

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <UserProfile queryRef={userRef} />
      <PostsList queryRef={postsRef} />
    </Suspense>
  );
}

function UserProfile({ queryRef }: { queryRef: QueryRef<UserData> }) {
  const { data } = useReadQuery(queryRef);

  return <div>User: {data.user.name}</div>;
}

function PostsList({ queryRef }: { queryRef: QueryRef<PostsData> }) {
  const { data } = useReadQuery(queryRef);

  return (
    <ul>
      {data.posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}
```

## Fetch Policies

Suspense hooks support most of the same fetch policies as `useQuery`, controlling how the query interacts with the cache. Note that `cache-only` and `standby` are not supported by Suspense hooks.

| Policy              | Description                                                |
| ------------------- | ---------------------------------------------------------- |
| `cache-first`       | Return cached data if available, otherwise fetch (default) |
| `cache-and-network` | Return cached data immediately, then fetch and update      |
| `network-only`      | Always fetch, update cache, ignore cached data             |
| `no-cache`          | Always fetch, never read or write cache                    |

### Usage Examples

```tsx
// Always fetch fresh data
const { data } = useSuspenseQuery(GET_NOTIFICATIONS, {
  fetchPolicy: "network-only",
});

// Prefer cached data
const { data } = useSuspenseQuery(GET_CATEGORIES, {
  fetchPolicy: "cache-first",
});

// Show cached data while fetching fresh data
const { data } = useSuspenseQuery(GET_POSTS, {
  fetchPolicy: "cache-and-network",
});
```

## Streaming SSR or React Server Components

Apollo Client integrates with modern React frameworks that support Streaming SSR and React Server Components. For detailed setup instructions specific to your framework, see:

- **Next.js App Router**: [setup-nextjs.md](./setup-nextjs.md) - Includes React Server Components, PreloadQuery component, and streaming SSR
- **React Router**: [setup-react-router.md](./setup-react-router.md) - Framework mode with SSR support
- **TanStack Start**: [setup-tanstack-start.md](./setup-tanstack-start.md) - Full-stack React framework with SSR

These guides cover:

- Framework-specific client setup and configuration
- Preloading queries for optimal performance
- Streaming SSR with `useBackgroundQuery` and Suspense
- Error handling in server-rendered environments

## Conditional Queries

### Using skipToken

Use `skipToken` to conditionally skip queries without TypeScript issues. When `skipToken` is used, the component won't suspend and `data` will be `undefined`.

```tsx
import { skipToken } from "@apollo/client";

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`;

function UserProfile({ userId }: { userId: string | null }) {
  const { data, dataState } = useSuspenseQuery(
    GET_USER,
    !userId
      ? skipToken
      : {
          variables: { id: userId },
        },
  );

  if (dataState !== "complete") {
    return <p>Select a user</p>;
  }

  return <Profile user={data.user} />;
}
```

### Conditional Rendering

Alternatively, use conditional rendering to control when Suspense hooks are called. This provides better type safety and clearer component logic.

```tsx
function UserProfile({ userId }: { userId: string | null }) {
  if (!userId) {
    return <p>Select a user</p>;
  }

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <UserDetails userId={userId} />
    </Suspense>
  );
}

function UserDetails({ userId }: { userId: string }) {
  const { data } = useSuspenseQuery(GET_USER, {
    variables: { id: userId },
  });

  return <Profile user={data.user} />;
}
```

> **Note**: Using conditional rendering with `skipToken` provides better type safety and avoids issues with required variables. The `skip` option is deprecated in favor of `skipToken`.

```

### references/mutations.md

```markdown
# Mutations Reference

## Table of Contents

- [useMutation Hook](#usemutation-hook)
- [Mutation Variables](#mutation-variables)
- [Loading and Error States](#loading-and-error-states)
- [Optimistic UI](#optimistic-ui)
- [Cache Updates](#cache-updates)
- [Refetch Queries](#refetch-queries)
- [Error Handling](#error-handling)

## useMutation Hook

The `useMutation` hook is used to execute GraphQL mutations.

### Basic Usage

```tsx
import { gql } from "@apollo/client";
import { useMutation } from "@apollo/client/react";

const ADD_TODO = gql`
  mutation AddTodo($text: String!) {
    addTodo(text: $text) {
      id
      text
      completed
    }
  }
`;

function AddTodo() {
  const [addTodo, { data, loading, error }] = useMutation(ADD_TODO);

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        const form = e.currentTarget;
        const text = new FormData(form).get("text") as string;
        addTodo({ variables: { text } });
        form.reset();
      }}
    >
      <input name="text" placeholder="Add todo" />
      <button type="submit" disabled={loading}>
        Add
      </button>
      {error && <p>Error: {error.message}</p>}
    </form>
  );
}
```

### Return Tuple

```typescript
const [
  mutateFunction, // Function to call to execute mutation
  {
    data, // Mutation result data
    loading, // True while mutation is in flight
    error, // ApolloError if mutation failed
    called, // True if mutation has been called
    reset, // Reset mutation state
    client, // Apollo Client instance
  },
] = useMutation(MUTATION);
```

## Mutation Variables

### Variables in Options

```tsx
const [createUser] = useMutation(CREATE_USER, {
  variables: {
    input: {
      name: "Default User",
      email: "[email protected]",
    },
  },
});

// Call with default variables
await createUser();

// Override variables
await createUser({
  variables: {
    input: {
      name: "Custom User",
      email: "[email protected]",
    },
  },
});
```

### TypeScript Types

Use `TypedDocumentNode` instead of generic type parameters:

```typescript
import { gql, TypedDocumentNode } from "@apollo/client";

interface CreateUserData {
  createUser: {
    id: string;
    name: string;
    email: string;
  };
}

interface CreateUserVariables {
  input: {
    name: string;
    email: string;
  };
}

const CREATE_USER: TypedDocumentNode<CreateUserData, CreateUserVariables> = gql`
  mutation CreateUser($input: CreateUserInput!) {
    createUser(input: $input) {
      id
      name
      email
    }
  }
`;

const [createUser, { data, loading }] = useMutation(CREATE_USER);

const { data } = await createUser({
  variables: {
    input: { name: "John", email: "[email protected]" },
  },
});

// data.createUser is fully typed
```

## Loading and Error States

### Handling in UI

```tsx
function CreatePost() {
  const [createPost, { loading, error, data, reset }] = useMutation(CREATE_POST);

  if (data) {
    return (
      <div>
        <p>Post created: {data.createPost.title}</p>
        <button onClick={reset}>Create another</button>
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" disabled={loading} />
      <textarea name="content" disabled={loading} />
      <button type="submit" disabled={loading}>
        {loading ? "Creating..." : "Create Post"}
      </button>
      {error && (
        <div className="error">
          <p>Failed to create post: {error.message}</p>
          <button onClick={reset}>Try again</button>
        </div>
      )}
    </form>
  );
}
```

### Async/Await Pattern

If you only need the promise without using the hook's loading/data state, use `client.mutate` instead:

```tsx
import { useApolloClient } from "@apollo/client/react";

function CreatePost() {
  const client = useApolloClient();

  async function handleSubmit(formData: FormData) {
    try {
      const { data } = await client.mutate({
        mutation: CREATE_POST,
        variables: {
          input: {
            title: formData.get("title"),
            content: formData.get("content"),
          },
        },
      });
      console.log("Created:", data.createPost);
      router.push(`/posts/${data.createPost.id}`);
    } catch (error) {
      console.error("Failed to create post:", error);
    }
  }

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        handleSubmit(new FormData(e.currentTarget));
      }}
    >
      ...
    </form>
  );
}
```

If you do use the hook's state, e.g. because you want to render the `loading` state, errors or returned `data`, you can also use the `useMutation` hook with `async..await` in your handler:

```tsx
function CreatePost() {
  const [createPost, { loading }] = useMutation(CREATE_POST);

  async function handleSubmit(formData: FormData) {
    try {
      const { data } = await createPost({
        variables: {
          input: {
            title: formData.get("title"),
            content: formData.get("content"),
          },
        },
      });
      console.log("Created:", data.createPost);
      router.push(`/posts/${data.createPost.id}`);
    } catch (error) {
      console.error("Failed to create post:", error);
    }
  }

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        handleSubmit(new FormData(e.currentTarget));
      }}
    >
      <button type="submit" disabled={loading}>
        {loading ? "Creating..." : "Create Post"}
      </button>
    </form>
  );
}
```

## Optimistic UI

Optimistic UI immediately reflects the expected result of a mutation before the server responds.

### Basic Optimistic Response

**Important**: `optimisticResponse` needs to be a full valid response for the mutation. A partial result might result in subtle errors.

```tsx
const [addTodo] = useMutation(ADD_TODO, {
  optimisticResponse: {
    addTodo: {
      __typename: "Todo",
      id: "temp-id",
      text: "New todo",
      completed: false,
    },
  },
});
```

### Dynamic Optimistic Response

```tsx
function TodoList() {
  const [addTodo] = useMutation(ADD_TODO);

  const handleAdd = (text: string) => {
    addTodo({
      variables: { text },
      optimisticResponse: {
        addTodo: {
          __typename: "Todo",
          id: `temp-${Date.now()}`,
          text,
          completed: false,
        },
      },
    });
  };

  return <AddTodoForm onAdd={handleAdd} />;
}
```

### Optimistic Response with Cache Update

```tsx
const [toggleTodo] = useMutation(TOGGLE_TODO, {
  optimisticResponse: ({ id }) => ({
    toggleTodo: {
      __typename: "Todo",
      id,
      completed: true, // Assume success
    },
  }),
  update: (cache, { data }) => {
    // This runs twice: once with optimistic data, once with server data
    cache.modify({
      id: cache.identify(data.toggleTodo),
      fields: {
        completed: () => data.toggleTodo.completed,
      },
    });
  },
});
```

## Cache Updates

### Using update Function

```tsx
const [addTodo] = useMutation(ADD_TODO, {
  update: (cache, { data }) => {
    // Read existing todos from cache
    const existingTodos = cache.readQuery<{ todos: Todo[] }>({
      query: GET_TODOS,
    });

    // Write updated list back to cache
    cache.writeQuery({
      query: GET_TODOS,
      data: {
        todos: [...(existingTodos?.todos ?? []), data.addTodo],
      },
    });
  },
});
```

### cache.modify

```tsx
const [deleteTodo] = useMutation(DELETE_TODO, {
  update: (cache, { data }) => {
    cache.modify({
      fields: {
        todos: (existingTodos: Reference[], { readField }) => {
          return existingTodos.filter((todoRef) => readField("id", todoRef) !== data.deleteTodo.id);
        },
      },
    });
  },
});
```

### cache.evict

```tsx
const [deleteUser] = useMutation(DELETE_USER, {
  update: (cache, { data }) => {
    // Remove the user object from cache entirely
    cache.evict({ id: cache.identify(data.deleteUser) });
    // Clean up dangling references
    cache.gc();
  },
});
```

### Updating Related Queries

```tsx
const [createPost] = useMutation(CREATE_POST, {
  update: (cache, { data }) => {
    // Update author's post count
    cache.modify({
      id: cache.identify({ __typename: "User", id: data.createPost.authorId }),
      fields: {
        postCount: (existing) => existing + 1,
        posts: (existing, { toReference }) => [...existing, toReference(data.createPost)],
      },
    });

    // Add to feed
    cache.modify({
      fields: {
        feed: (existing, { toReference }) => [toReference(data.createPost), ...existing],
      },
    });
  },
});
```

## Refetch Queries

### Basic Refetch

There are three refetch notations:

- **String**: `refetchQueries: ['getTodos']` - refetches all active `getTodos` queries
- **Query document**: `refetchQueries: [GET_TODOS]` - refetches all active queries using this document
- **Object**: `refetchQueries: [{ query: GET_TODOS }, { query: GET_TODOS, variables: { page: 25 } }]` - **fetches** the query, regardless if it's actively used in the UI

```tsx
const [addTodo] = useMutation(ADD_TODO, {
  // Refetch all active GET_TODOS queries
  refetchQueries: ["getTodos"],
  // Or: refetchQueries: [GET_TODOS],
});

// Fetch specific query with variables (even if not active)
const [addTodo] = useMutation(ADD_TODO, {
  refetchQueries: [{ query: GET_TODOS }, { query: GET_TODO_COUNT }],
});
```

### Conditional Refetch

```tsx
const [addTodo] = useMutation(ADD_TODO, {
  refetchQueries: (result) => {
    if (result.data?.addTodo.priority === "HIGH") {
      return [{ query: GET_HIGH_PRIORITY_TODOS }];
    }
    return [{ query: GET_TODOS }];
  },
});
```

### Refetch Active Queries

```tsx
const [addTodo] = useMutation(ADD_TODO, {
  refetchQueries: "active", // Refetch all active queries
  // Or: 'all' to refetch all queries (including inactive)
});
```

### awaitRefetchQueries

```tsx
const [addTodo] = useMutation(ADD_TODO, {
  refetchQueries: [{ query: GET_TODOS }],
  awaitRefetchQueries: true, // Wait for refetch before resolving mutation
});
```

### onQueryUpdated

Returning `true` from `onQueryUpdated` causes a refetch. Don't call `refetch()` manually inside `onQueryUpdated`, as it won't retain the query and might cancel it early.

```tsx
const [addTodo] = useMutation(ADD_TODO, {
  update: (cache, { data }) => {
    // Update cache...
  },
  onQueryUpdated: (observableQuery) => {
    // Called for each query affected by cache update
    console.log(`Query ${observableQuery.queryName} was updated`);
    // Return true to refetch
    return true;
  },
});
```

## Error Handling

### Error Policy

```tsx
const [createUser, { loading }] = useMutation(CREATE_USER, {
  errorPolicy: "all", // Return both data and errors
});

const { data, errors } = await createUser({
  variables: { input },
});

// Handle partial success
if (data?.createUser) {
  console.log("User created:", data.createUser);
}
if (errors) {
  console.warn("Some errors occurred:", errors);
}
```

### onError Callback

```tsx
const [createUser] = useMutation(CREATE_USER, {
  onError: (error) => {
    // Handle error globally
    toast.error(`Failed to create user: ${error.message}`);

    // Log to error tracking service
    Sentry.captureException(error);
  },
  onCompleted: (data) => {
    toast.success(`User ${data.createUser.name} created!`);
  },
});
```

### Field-Level Errors

```tsx
const [createUser] = useMutation(CREATE_USER, {
  errorPolicy: "all",
});

const handleSubmit = async (input: CreateUserInput) => {
  const { data, errors } = await createUser({
    variables: { input },
  });

  // Handle GraphQL validation errors
  const fieldErrors = errors?.reduce(
    (acc, error) => {
      const field = error.extensions?.field as string;
      if (field) {
        acc[field] = error.message;
      }
      return acc;
    },
    {} as Record<string, string>,
  );

  if (fieldErrors?.email) {
    setEmailError(fieldErrors.email);
  }
};
```

```

### references/fragments.md

```markdown
# Fragments Reference

GraphQL fragments define a set of fields for a specific type. In Apollo Client, fragments are especially powerful when colocated with components to define each component's data requirements independently, creating a clear separation of concerns and enabling better component composition.

## Table of Contents

- [What Are Fragments](#what-are-fragments)
- [Basic Fragment Syntax](#basic-fragment-syntax)
- [Fragment Colocation](#fragment-colocation)
- [Fragment Reading Hooks](#fragment-reading-hooks)
- [Data Masking](#data-masking)
- [Fragment Registry](#fragment-registry)
- [TypeScript Integration](#typescript-integration)
- [Best Practices](#best-practices)

## What Are Fragments

A GraphQL fragment defines a set of fields for a specific GraphQL type. Fragments are defined on a specific GraphQL type and can be included in operations using the spread operator (`...`).

In Apollo Client, fragments serve a specific purpose:

**Fragments are for colocation, not reuse.** Each component should declare its data needs in a dedicated fragment. This allows components to independently evolve their data requirements without creating artificial dependencies between unrelated parts of your application.

Fragments enable:

1. **Component colocation**: Define the exact data requirements for a component alongside the component code
2. **Independent evolution**: Change a component's data needs without affecting other components
3. **Code organization**: Compose fragments together to build complete queries that mirror your component hierarchy

## Basic Fragment Syntax

### Defining a Fragment

```typescript
import { gql } from "@apollo/client";

const USER_FRAGMENT = gql`
  fragment UserFields on User {
    id
    name
    email
    avatarUrl
  }
`;
```

Every fragment includes:

- A unique name (`UserFields`)
- The type it operates on (`User`)
- The fields to select

### Using Fragments in Queries

Include fragments in queries using the spread operator:

```typescript
const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      ...UserFields
    }
  }

  ${USER_FRAGMENT}
`;
```

When using GraphQL Code Generator with the recommended configuration (typescript, typescript-operations, and typed-document-node plugins), fragments defined in your source files are automatically picked up and generated into typed document nodes. The generated fragment documents already include the fragment definition, so you don't need to interpolate them manually into queries.

## Fragment Colocation

Fragment colocation is the practice of defining fragments in the same file as the component that uses them. This creates a clear contract between components and their data requirements.

### Why Colocate Fragments

- **Locality**: Data requirements live next to the code that uses them
- **Maintainability**: Changes to component UI and data needs happen together
- **Type safety**: TypeScript can infer exact types from colocated fragments
- **Independence**: Components can evolve their data requirements without affecting other components

### Colocation Pattern

The recommended pattern for colocating fragments with components:

```tsx
import { gql, FragmentType } from "@apollo/client";
import { useSuspenseFragment } from "@apollo/client/react";

// Fragment definition
// This will be picked up by Codegen to create `UserCard_UserFragmentDoc` in `./fragments.generated.ts`.
// As that generated fragment document is correctly typed, we use that in the code going forward.
// This fragment will never be consumed in runtime code, so it is wrapped in `if (false)` so the bundler can omit it when bundling.
if (false) {
  gql`
    fragment UserCard_user on User {
      id
      name
      email
      avatarUrl
    }
  `;
}

// This has been created from above fragment definition by CodeGen and is a correctly typed `TypedDocumentNode`
import { UserCard_UserFragmentDoc } from "./fragments.generated.ts";

// Component receives the (partially masked) parent object
export function UserCard({ user }: { user: FragmentType<typeof UserCard_UserFragmentDoc> }) {
  // Creates a subscription to the fragment in the cache
  const { data } = useSuspenseFragment({
    fragment: UserCard_UserFragmentDoc,
    fragmentName: "UserCard_user",
    from: user,
  });

  return (
    <div>
      <img src={data.avatarUrl} alt={data.name} />
      <h2>{data.name}</h2>
      <p>{data.email}</p>
    </div>
  );
}
```

### Naming Convention

A suggested naming pattern for fragments follows this convention:

```
{ComponentName}_{propName}
```

Where `propName` is the name of the prop the component receives containing the fragment data.

Examples:

- `UserCard_user` - Fragment for the `user` prop in the UserCard component
- `PostList_posts` - Fragment for the `posts` prop in the PostList component
- `CommentItem_comment` - Fragment for the `comment` prop in the CommentItem component

This convention makes it clear which component owns which fragment. However, you can choose a different naming convention based on your project's needs.

**Note**: A component might accept fragment data through multiple props, in which case it would have multiple associated fragments. For example, a `CommentCard` component might accept both a `comment` prop and an `author` prop, resulting in `CommentCard_comment` and `CommentCard_author` fragments.

### Composing Fragments

Parent components compose child fragments to build complete queries:

```tsx
// Child component
import { gql } from "@apollo/client";

if (false) {
  gql`
    fragment UserAvatar_user on User {
      id
      avatarUrl
      name
    }
  `;
}

// Parent component composes child fragments
if (false) {
  gql`
    fragment UserProfile_user on User {
      id
      name
      email
      bio
      ...UserAvatar_user
    }
  `;
}

// Page-level query composes all fragments
if (false) {
  gql`
    query UserProfilePage($id: ID!) {
      user(id: $id) {
        ...UserProfile_user
      }
    }
  `;
}
```

This creates a hierarchy that mirrors your component tree.

## Fragment Reading Hooks

Apollo Client provides hooks to read fragment data within components. These hooks work with data masking to ensure components only access the data they explicitly requested.

### useSuspenseFragment

For components using Suspense and concurrent features:

```tsx
import { useSuspenseFragment } from "@apollo/client/react";
import { FragmentType } from "@apollo/client";
import { UserCard_UserFragmentDoc } from "./fragments.generated";

function UserCard({ user }: { user: FragmentType<typeof UserCard_UserFragmentDoc> }) {
  const { data } = useSuspenseFragment({
    fragment: UserCard_UserFragmentDoc,
    fragmentName: "UserCard_user",
    from: user,
  });

  return <div>{data.name}</div>;
}
```

### useFragment

For components not using Suspense:

```tsx
import { useFragment } from "@apollo/client/react";
import { FragmentType } from "@apollo/client";
import { UserCard_UserFragmentDoc } from "./fragments.generated";

function UserCard({ user }: { user: FragmentType<typeof UserCard_UserFragmentDoc> }) {
  const { data, complete } = useFragment({
    fragment: UserCard_UserFragmentDoc,
    fragmentName: "UserCard_user",
    from: user,
  });

  if (!complete) {
    return <div>Loading...</div>;
  }

  return <div>{data.name}</div>;
}
```

The `complete` field indicates whether all fragment data is available in the cache.

### Hook Options

Both hooks accept these options:

```typescript
{
  // The fragment document (required)
  fragment: TypedDocumentNode,

  // The fragment name (optional in most cases)
  // Only required if the fragment document contains multiple definitions
  fragmentName?: string,

  // The source data containing the fragment (required)
  // Can be a single object or an array of objects
  from: FragmentType<typeof fragment> | Array<FragmentType<typeof fragment>>,

  // Variables for the fragment (optional)
  variables?: Variables,
}
```

When `from` is an array, the hook returns an array of results, allowing you to read fragments from multiple objects efficiently. **Note**: Array support for the `from` parameter was added in Apollo Client 4.1.0.

## Data Masking

Data masking is a feature that prevents components from accessing data they didn't explicitly request through their fragments. This enforces proper data boundaries and prevents over-rendering.

### Enabling Data Masking

Enable data masking when creating your Apollo Client:

```typescript
import { ApolloClient, InMemoryCache } from "@apollo/client";

const client = new ApolloClient({
  cache: new InMemoryCache(),
  dataMasking: true, // Enable data masking
});
```

### How Data Masking Works

With data masking enabled:

1. Fragments return opaque `FragmentType` objects
2. Components must use `useFragment` or `useSuspenseFragment` to unmask data
3. Components can only access fields defined in their own fragments
4. TypeScript enforces these boundaries at compile time

Without data masking:

```tsx
// ❌ Without data masking - component can access any data from parent
function UserCard({ user }: { user: User }) {
  // Can access any User field, even if not in fragment
  return <div>{user.privateData}</div>;
}
```

With data masking:

```tsx
// ✅ With data masking - component can only access its fragment data
import { UserCard_UserFragmentDoc } from "./fragments.generated";

function UserCard({ user }: { user: FragmentType<typeof UserCard_UserFragmentDoc> }) {
  const { data } = useSuspenseFragment({
    fragment: UserCard_UserFragmentDoc,
    from: user,
  });

  // TypeScript error: 'privateData' doesn't exist on fragment type
  // return <div>{data.privateData}</div>;

  // Only fields from the fragment are accessible
  return <div>{data.name}</div>;
}
```

### Benefits of Data Masking

- **Prevents over-rendering**: Components only re-render when their specific data changes
- **Enforces boundaries**: Components can't accidentally depend on data they don't own
- **Better refactoring**: Safe to modify parent queries without breaking child components
- **Type safety**: TypeScript catches attempts to access unavailable fields

## Fragment Registry

The fragment registry is an **alternative approach** to GraphQL Code Generator's automatic fragment inlining by name. It allows you to register fragments globally, making them available throughout your application by name reference.

**Important**: GraphQL Code Generator automatically inlines fragments by name wherever they're used in your queries. Either approach is sufficient on its own—**you don't need to combine them**.

### Creating a Fragment Registry

```typescript
import { ApolloClient, InMemoryCache } from "@apollo/client";
import { createFragmentRegistry } from "@apollo/client/cache";

export const fragmentRegistry = createFragmentRegistry();

const client = new ApolloClient({
  cache: new InMemoryCache({
    fragments: fragmentRegistry,
  }),
});
```

### Registering Fragments

Register fragments after defining them:

```typescript
import { gql } from "@apollo/client";
import { fragmentRegistry } from "./apollo/client";

const USER_FRAGMENT = gql`
  fragment UserFields on User {
    id
    name
    email
  }
`;

fragmentRegistry.register(USER_FRAGMENT);
```

With colocated fragments:

```tsx
import { fragmentRegistry } from "@/apollo/client";
import { UserCard_UserFragmentDoc } from "./fragments.generated";

// Register the fragment globally
fragmentRegistry.register(UserCard_UserFragmentDoc);
```

### Using Registered Fragments

Once registered, fragments can be referenced by name in queries without explicit imports:

```tsx
// Fragment is available by name because it's registered
const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      ...UserCard_user
    }
  }
`;
```

### Approaches for Fragment Composition

There are three approaches to make child fragments available in parent queries:

1. **GraphQL Code Generator inlining** (Recommended): CodeGen automatically inlines fragments by name. No manual work needed—just reference fragments by name in your queries.

2. **Fragment Registry**: Manually register fragments to make them available by name. Useful for runtime scenarios where CodeGen isn't available.

3. **Manual interpolation**: Explicitly import and interpolate child fragments into parent fragments:

   ```typescript
   import { CHILD_FRAGMENT } from "./ChildComponent";

   const PARENT_FRAGMENT = gql`
     fragment Parent_data on Data {
       field
       ...Child_data
     }
     ${CHILD_FRAGMENT}
   `;
   ```

### Pros and Cons

**GraphQL Code Generator inlining**:

- ✅ Less work: Automatic, no manual registration needed
- ❌ Larger bundle: Fragments are inlined into every query that uses them

**Fragment Registry**:

- ✅ Smaller bundle: Fragments are registered once, referenced by name
- ❌ More work: Requires manual registration of each fragment
- ❌ May cause issues with lazy-loaded modules if the module is not loaded before the query is executed
- ✅ Best for deeply nested component trees where bundle size matters

**Manual interpolation**:

- ❌ Most work: Manual imports and interpolation required
- ✅ Explicit: Clear fragment dependencies in code

### Recommendation

For most applications using GraphQL Code Generator (as shown in this guide), **use the automatic inlining**—it requires no additional setup and works seamlessly. Consider the fragment registry only if bundle size becomes a concern in applications with deeply nested component trees.

## TypeScript Integration

Apollo Client provides strong TypeScript support for fragments through GraphQL Code Generator.

### Generated Types

GraphQL Code Generator produces typed fragment documents:

```typescript
// Generated file: fragments.generated.ts
export type UserCard_UserFragment = {
  __typename: "User";
  id: string;
  name: string;
  email: string;
  avatarUrl: string;
} & { " $fragmentName"?: "UserCard_UserFragment" };

export const UserCard_UserFragmentDoc: TypedDocumentNode<UserCard_UserFragment, never>;
```

### Type-Safe Fragment Usage

Use `FragmentType` to accept masked fragment data:

```tsx
import { FragmentType } from "@apollo/client";
import { UserCard_UserFragmentDoc } from "./fragments.generated";

function UserCard({ user }: { user: FragmentType<typeof UserCard_UserFragmentDoc> }) {
  const { data } = useSuspenseFragment({
    fragment: UserCard_UserFragmentDoc,
    from: user,
  });

  // 'data' is fully typed as UserCard_UserFragment
  return <div>{data.name}</div>;
}
```

### Fragment Type Inference

TypeScript infers types from fragment documents automatically:

```tsx
import { UserCard_UserFragmentDoc } from "./fragments.generated";

// Types are inferred from the fragment
const { data } = useSuspenseFragment({
  fragment: UserCard_UserFragmentDoc,
  from: user,
});

// data.name is string
// data.email is string
// data.nonExistentField is a TypeScript error
```

### Parent-Child Type Safety

When passing fragment data from parent to child:

```tsx
// Parent query
const { data } = useSuspenseQuery(GET_USER);

// TypeScript ensures the query includes UserCard_user fragment
// before allowing it to be passed to UserCard
<UserCard user={data.user} />;
```

## Best Practices

### Prefer Colocation Over Reuse

**Fragments are for colocation, not reuse.** Each component should declare its data needs in a dedicated fragment, even if multiple components currently need the same fields.

Sharing fragments between components just because they happen to need the same fields today creates artificial dependencies. When one component's requirements change, the shared fragment must be updated, causing all components using it to over-fetch data they don't need.

```tsx
// ✅ Good: Each component has its own fragment
if (false) {
  gql`
    fragment UserCard_user on User {
      id
      name
      email
      avatarUrl
    }
  `;

  gql`
    fragment UserListItem_user on User {
      id
      name
      email
    }
  `;
}

// If UserCard later needs 'bio', only UserCard_user changes
// UserListItem doesn't over-fetch 'bio'
```

```tsx
// ❌ Avoid: Sharing a generic fragment across components
const COMMON_USER_FIELDS = gql`
  fragment CommonUserFields on User {
    id
    name
    email
  }
`;

// UserCard and UserListItem both use CommonUserFields
// When UserCard needs 'bio', adding it to CommonUserFields
// causes UserListItem to over-fetch unnecessarily
```

This independence allows each component to evolve its data requirements without affecting unrelated parts of your application.

### One Query Per Page

Compose all page data requirements into a single query at the page level:

```tsx
// ✅ Good: Single page-level query
if (false) {
  gql`
    query UserProfilePage($id: ID!) {
      user(id: $id) {
        ...UserHeader_user
        ...UserPosts_user
        ...UserFriends_user
      }
    }
  `;
}
```

```tsx
// ❌ Avoid: Multiple queries in different components
function UserProfile() {
  const { data: userData } = useQuery(GET_USER);
  const { data: postsData } = useQuery(GET_USER_POSTS);
  const { data: friendsData } = useQuery(GET_USER_FRIENDS);
  // ...
}
```

### Use Fragment-Reading Hooks in Components

Non-page components should use `useFragment` or `useSuspenseFragment`:

```tsx
// ✅ Good: Component reads fragment data
import { FragmentType } from "@apollo/client";
import { useSuspenseFragment } from "@apollo/client/react";
import { UserCard_UserFragmentDoc } from "./fragments.generated";

function UserCard({ user }: { user: FragmentType<typeof UserCard_UserFragmentDoc> }) {
  const { data } = useSuspenseFragment({
    fragment: UserCard_UserFragmentDoc,
    from: user,
  });
  return <div>{data.name}</div>;
}
```

```tsx
// ❌ Avoid: Component uses query hook
function UserCard({ userId }: { userId: string }) {
  const { data } = useQuery(GET_USER, { variables: { id: userId } });
  return <div>{data.user.name}</div>;
}
```

### Request Only Required Fields

Keep fragments minimal and only request fields the component actually uses:

```tsx
// ✅ Good: Only necessary fields
if (false) {
  gql`
    fragment UserListItem_user on User {
      id
      name
    }
  `;
}
```

```tsx
// ❌ Avoid: Requesting unused fields
if (false) {
  gql`
    fragment UserListItem_user on User {
      id
      name
      email
      bio
      friends {
        id
        name
      }
      posts {
        id
        title
      }
    }
  `;
}
```

### Use @defer for Below-the-Fold Content

The `@defer` directive allows you to defer loading of non-critical fields, enabling faster initial page loads by prioritizing essential data. The deferred fields are delivered via incremental delivery and arrive after the non-deferred data, allowing the UI to progressively render as data becomes available.

Defer slow fields that aren't immediately visible:

```tsx
if (false) {
  gql`
    query ProductPage($id: ID!) {
      product(id: $id) {
        id
        name
        price
        ...ProductReviews_product @defer
      }
    }
  `;
}
```

This allows the page to render quickly while reviews load in the background.

### Handle Client-Only Fields

Use the `@client` directive for fields resolved locally:

```tsx
if (false) {
  gql`
    fragment TodoItem_todo on Todo {
      id
      text
      completed
      isSelected @client
    }
  `;
}
```

### Enable Data Masking for New Applications

Always enable data masking in new applications:

```typescript
const client = new ApolloClient({
  cache: new InMemoryCache(),
  dataMasking: true,
});
```

This enforces proper boundaries from the start and prevents accidental coupling between components.

## Apollo Client Data Masking vs GraphQL-Codegen Fragment Masking

Apollo Client's data masking and GraphQL Code Generator's fragment masking are different features that serve different purposes:

### GraphQL-Codegen Fragment Masking

GraphQL Code Generator's fragment masking (when using the client preset) is a **type-level** feature:

- Masks data only at the TypeScript type level
- The actual runtime data remains fully accessible on the object
- Using their `useFragment` hook simply "unmasks" the data on a type level
- Does not prevent accidental access to data at runtime
- Parent components receive all data and pass it down
- This means the parent component has to be subscribed to all data

### Apollo Client Data Masking

Apollo Client's data masking is a **runtime** feature with significant performance benefits:

- Removes data at the runtime level, not just the type level
- The `useFragment` and `useSuspenseFragment` hooks create cache subscriptions
- Parent objects are sparse and only contain unmasked data
- Prevents accidental access to data that should be masked

### Key Benefits of Apollo Client Data Masking

**1. No Accidental Data Access**

With runtime data masking, masked fields are not present in the parent object at all. You cannot accidentally access them, even if you bypass TypeScript type checking.

**2. Fewer Re-renders**

Apollo Client's approach creates more efficient subscriptions:

- **Without data masking**: Parent component subscribes to all fields (including masked ones). When a masked child field changes, the parent re-renders to pass that runtime data down the tree.
- **With data masking**: Parent component only subscribes to its own unmasked fields. Subscriptions on masked fields happen lower in the React component tree when the child component calls `useSuspenseFragment`. When a masked field changes, only the child component that subscribed to it re-renders.

### Example

```tsx
import { FragmentType } from "@apollo/client";
import { useSuspenseQuery, useSuspenseFragment } from "@apollo/client/react";
import { UserCard_UserFragmentDoc } from "./fragments.generated";

function ParentComponent() {
  const { data } = useSuspenseQuery(GET_USER);

  // With Apollo Client data masking:
  // - data.user only contains unmasked fields
  // - Parent doesn't re-render when child-specific fields change

  return <UserCard user={data.user} />;
}

function UserCard({ user }: { user: FragmentType<typeof UserCard_UserFragmentDoc> }) {
  // Creates a cache subscription specifically for UserCard_user fields
  const { data } = useSuspenseFragment({
    fragment: UserCard_UserFragmentDoc,
    from: user,
  });

  // Only this component re-renders when these fields change
  return <div>{data.name}</div>;
}
```

This granular subscription approach improves performance in large applications with deeply nested component trees.

```

### references/caching.md

```markdown
# Caching Reference

## Table of Contents

- [InMemoryCache Setup](#inmemorycache-setup)
- [Cache Normalization](#cache-normalization)
- [Type Policies](#type-policies)
- [Field Policies](#field-policies)
- [Pagination](#pagination)
- [Cache Manipulation](#cache-manipulation)
- [Garbage Collection](#garbage-collection)

## InMemoryCache Setup

### Basic Configuration

```typescript
import { InMemoryCache } from "@apollo/client";

const cache = new InMemoryCache({
  // Custom type policies
  typePolicies: {
    Query: {
      fields: {
        // Query-level field policies
      },
    },
    User: {
      keyFields: ["id"],
      fields: {
        // User-level field policies
      },
    },
  },

  // Custom type name handling (rare)
  possibleTypes: {
    Character: ["Human", "Droid"],
    Node: ["User", "Post", "Comment"],
  },
});
```

### Constructor Options

```typescript
new InMemoryCache({
  // Define how types are identified in cache
  typePolicies: {
    /* ... */
  },

  // Interface/union type mappings between supertypes and their subtypes
  possibleTypes: {
    /* ... */
  },

  // Custom function to generate cache IDs (rare)
  dataIdFromObject: (object) => {
    if (object.__typename === "Book") {
      return `Book:${object.isbn}`;
    }
    return defaultDataIdFromObject(object);
  },
});
```

## Cache Normalization

Apollo Client normalizes data by splitting query results into individual objects and storing them by unique identifier.

### How Normalization Works

```graphql
# Query
query GetPost {
  post(id: "1") {
    id
    title
    author {
      id
      name
    }
  }
}
```

```typescript
// Normalized cache structure
{
  'Post:1': {
    __typename: 'Post',
    id: '1',
    title: 'Hello World',
    author: { __ref: 'User:42' }
  },
  'User:42': {
    __typename: 'User',
    id: '42',
    name: 'John'
  },
  ROOT_QUERY: {
    'post({"id":"1"})': { __ref: 'Post:1' }
  }
}
```

### Benefits of Normalization

1. **Automatic updates**: When `User:42` is updated anywhere, all components showing that user update
2. **Deduplication**: Same objects aren't stored multiple times
3. **Efficient updates**: Only changed objects trigger re-renders

## Type Policies

### keyFields

Customize how objects are identified in the cache.

```typescript
const cache = new InMemoryCache({
  typePolicies: {
    // Use ISBN instead of id for books
    Book: {
      keyFields: ["isbn"],
    },

    // Composite key
    UserSession: {
      keyFields: ["userId", "deviceId"],
    },

    // Nested key
    Review: {
      keyFields: ["book", ["isbn"], "reviewer", ["id"]],
    },

    // No key fields (singleton, only one object in cache per type)
    AppSettings: {
      keyFields: [],
    },

    // Disable normalization (objects of this type will be stored with their
    // parent entity. The same object might end up multiple times in the cache
    // and run out of sync. Use with caution, only if this object really relates
    // to a property of their parent entity and cannot exist on its own.)
    Address: {
      keyFields: false,
    },
  },
});
```

### merge Functions

Control how incoming data merges with existing data.

```typescript
const cache = new InMemoryCache({
  typePolicies: {
    User: {
      fields: {
        // Deep merge profile object
        profile: {
          merge: true, // Shorthand for deep merge
        },

        // Custom merge logic
        notifications: {
          merge(existing = [], incoming, { mergeObjects }) {
            // Prepend new notifications
            return [...incoming, ...existing];
          },
        },
      },
    },
  },
});
```

## Field Policies

### read Function

Transform cached data when reading.

```typescript
const cache = new InMemoryCache({
  typePolicies: {
    User: {
      fields: {
        // Computed field
        fullName: {
          read(_, { readField }) {
            const firstName = readField("firstName");
            const lastName = readField("lastName");
            return `${firstName} ${lastName}`;
          },
        },

        // Transform existing field
        birthDate: {
          read(existing) {
            return existing ? new Date(existing) : null;
          },
        },

        // Default value
        role: {
          read(existing = "USER") {
            return existing;
          },
        },
      },
    },
  },
});
```

### merge Function

Control how incoming data is stored.

```typescript
const cache = new InMemoryCache({
  typePolicies: {
    User: {
      fields: {
        // Accumulate items instead of replacing
        friends: {
          merge(existing = [], incoming) {
            return [...existing, ...incoming];
          },
        },

        // Merge objects deeply
        settings: {
          merge(existing, incoming, { mergeObjects }) {
            return mergeObjects(existing, incoming);
          },
        },
      },
    },

    Query: {
      fields: {
        // Merge paginated results
        posts: {
          keyArgs: ["category"], // Only category affects cache key
          merge(existing = { items: [] }, incoming) {
            return {
              ...incoming,
              items: [...existing.items, ...incoming.items],
            };
          },
        },
      },
    },
  },
});
```

### keyArgs

Control which arguments affect cache storage.

```typescript
const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        // Different cache entry per userId only
        // (limit, offset don't create new entries)
        userPosts: {
          keyArgs: ["userId"],
        },

        // No arguments affect cache key
        // (useful for pagination)
        feed: {
          keyArgs: false,
        },

        // Nested argument
        search: {
          keyArgs: ["filter", ["category", "status"]],
        },
      },
    },
  },
});
```

## Pagination

### Offset-Based Pagination

```typescript
import { offsetLimitPagination } from "@apollo/client/utilities";

const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        posts: offsetLimitPagination(),

        // With key arguments
        userPosts: offsetLimitPagination(["userId"]),
      },
    },
  },
});
```

### Cursor-Based Pagination (Relay Style)

```typescript
import { relayStylePagination } from "@apollo/client/utilities";

const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        posts: relayStylePagination(),

        // With key arguments
        userPosts: relayStylePagination(["userId"]),
      },
    },
  },
});
```

### Custom Pagination

```typescript
const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        feed: {
          keyArgs: false,

          merge(existing, incoming, { args }) {
            const merged = existing ? existing.slice(0) : [];
            const offset = args?.offset ?? 0;

            for (let i = 0; i < incoming.length; i++) {
              merged[offset + i] = incoming[i];
            }

            return merged;
          },

          read(existing, { args }) {
            const offset = args?.offset ?? 0;
            const limit = args?.limit ?? existing?.length ?? 0;
            return existing?.slice(offset, offset + limit);
          },
        },
      },
    },
  },
});
```

### fetchMore for Pagination

```tsx
function PostList() {
  const { data, fetchMore, loading } = useQuery(GET_POSTS, {
    variables: { offset: 0, limit: 10 },
  });

  const loadMore = () => {
    fetchMore({
      variables: {
        offset: data.posts.length,
      },
      // With proper type policies, no updateQuery needed
    });
  };

  return (
    <div>
      {data?.posts.map((post) => (
        <PostCard key={post.id} post={post} />
      ))}
      <button onClick={loadMore} disabled={loading}>
        Load More
      </button>
    </div>
  );
}
```

## Cache Manipulation

### cache.readQuery

```typescript
// Read data from cache
const data = cache.readQuery({
  query: GET_TODOS,
});

// With variables
const userData = cache.readQuery({
  query: GET_USER,
  variables: { id: "1" },
});
```

### cache.writeQuery

```typescript
// Write data to cache
cache.writeQuery({
  query: GET_TODOS,
  data: {
    todos: [{ __typename: "Todo", id: "1", text: "Buy milk", completed: false }],
  },
});

// With variables
cache.writeQuery({
  query: GET_USER,
  variables: { id: "1" },
  data: {
    user: { __typename: "User", id: "1", name: "John" },
  },
});
```

### cache.readFragment / cache.writeFragment

```typescript
// Read a specific object - use cache.identify for safety
const user = cache.readFragment({
  id: cache.identify({ __typename: "User", id: "1" }),
  fragment: gql`
    fragment UserFragment on User {
      id
      name
      email
    }
  `,
});

// Apollo Client 4.1+: Use 'from' parameter (recommended)
const user = cache.readFragment({
  from: { __typename: "User", id: "1" },
  fragment: gql`
    fragment UserFragment on User {
      id
      name
      email
    }
  `,
});

// Update a specific object
cache.writeFragment({
  id: cache.identify({ __typename: "User", id: "1" }),
  fragment: gql`
    fragment UpdateUser on User {
      name
    }
  `,
  data: {
    name: "Jane",
  },
});

// Apollo Client 4.1+: Use 'from' parameter (recommended)
cache.writeFragment({
  from: { __typename: "User", id: "1" },
  fragment: gql`
    fragment UpdateUser on User {
      name
    }
  `,
  data: {
    name: "Jane",
  },
});
```

### cache.modify

```typescript
// Modify fields directly
cache.modify({
  id: cache.identify(user),
  fields: {
    // Set new value
    name: () => "New Name",

    // Transform existing value
    postCount: (existing) => existing + 1,

    // Delete field
    temporaryField: (_, { DELETE }) => DELETE,

    // Add to array
    friends: (existing, { toReference }) => [...existing, toReference({ __typename: "User", id: "2" })],
  },
});
```

### cache.evict

```typescript
// Remove object from cache
cache.evict({ id: "User:1" });

// Remove specific field
cache.evict({ id: "User:1", fieldName: "friends" });

// Remove with broadcast (trigger re-renders)
cache.evict({ id: "User:1", broadcast: true });
```

## Garbage Collection

### Manual Garbage Collection

```typescript
// After evicting objects, clean up dangling references
cache.evict({ id: "User:1" });
cache.gc();
```

### Retaining Objects

```typescript
// Prevent objects from being garbage collected
const release = cache.retain("User:1");

// Later, allow GC
release();
cache.gc();
```

### Inspecting Cache

```typescript
// Get all cached data
const cacheContents = cache.extract();

// Restore cache state
cache.restore(previousCacheContents);

// Get identified object cache key
const userId = cache.identify({ __typename: "User", id: "1" });
// Returns: 'User:1'
```

```

### references/state-management.md

```markdown
# State Management Reference

## Table of Contents

- [Reactive Variables](#reactive-variables)
- [Local-Only Fields](#local-only-fields)
- [Type Policies for Local State](#type-policies-for-local-state)
- [Combining Remote and Local State](#combining-remote-and-local-state)
- [useReactiveVar Hook](#usereactivevar-hook)

## Reactive Variables

Reactive variables are a way to store local state outside of the Apollo Client cache while still triggering reactive updates.

**Important**: Reactive variables store a single value that notifies `ApolloClient` instances when changed. They do not have separate values per ApolloClient instance. In multi-user environments like SSR, global or module-level reactive variables could be shared between users and cause data leaks. In frameworks that use SSR, always avoid storing reactive variables as globals.

### Creating Reactive Variables

```typescript
import { makeVar } from "@apollo/client";

// Simple reactive variable
export const isLoggedInVar = makeVar<boolean>(false);

// Object reactive variable
export const cartItemsVar = makeVar<CartItem[]>([]);

// Complex state
interface AppState {
  theme: "light" | "dark";
  sidebarOpen: boolean;
  notifications: Notification[];
}

export const appStateVar = makeVar<AppState>({
  theme: "light",
  sidebarOpen: true,
  notifications: [],
});
```

### Reading Reactive Variables

```tsx
// Direct read (non-reactive)
const isLoggedIn = isLoggedInVar();

// Reactive read in component
import { useReactiveVar } from "@apollo/client/react";

function AuthButton() {
  const isLoggedIn = useReactiveVar(isLoggedInVar);

  return isLoggedIn ? (
    <button onClick={() => isLoggedInVar(false)}>Logout</button>
  ) : (
    <button onClick={() => isLoggedInVar(true)}>Login</button>
  );
}
```

### Updating Reactive Variables

```typescript
// Set new value
isLoggedInVar(true);

// Update based on current value
cartItemsVar([...cartItemsVar(), newItem]);

// Update object state
appStateVar({
  ...appStateVar(),
  theme: "dark",
});

// Helper function pattern
export function toggleSidebar() {
  const current = appStateVar();
  appStateVar({ ...current, sidebarOpen: !current.sidebarOpen });
}

export function addNotification(notification: Notification) {
  const current = appStateVar();
  appStateVar({
    ...current,
    notifications: [...current.notifications, notification],
  });
}
```

## Local-Only Fields

Local-only fields are fields defined in queries but resolved entirely on the client using the `@client` directive.

**Important**: To use any `@client` fields, you need to add `LocalState` to the `ApolloClient` initialization:

```typescript
import { ApolloClient, InMemoryCache } from "@apollo/client";
import { LocalState } from "@apollo/client/local-state";

const client = new ApolloClient({
  cache: new InMemoryCache(),
  localState: new LocalState({}),
  // ... other options
});
```

> **Note**: `LocalState` is an Apollo Client 4.x concept and did not exist as a class in previous versions. In previous versions, a `localState` option was not necessary, and local resolvers (if used) could be passed directly to the `ApolloClient` constructor.

### Basic @client Fields

```tsx
const GET_USER_WITH_LOCAL = gql`
  query GetUserWithLocal($id: ID!) {
    user(id: $id) {
      id
      name
      email
      # Local-only fields
      isSelected @client
      displayName @client
    }
  }
`;

function UserCard({ userId }: { userId: string }) {
  const { data } = useQuery(GET_USER_WITH_LOCAL, {
    variables: { id: userId },
  });

  return (
    <div className={data?.user.isSelected ? "selected" : ""}>
      <h2>{data?.user.displayName}</h2>
      <p>{data?.user.email}</p>
    </div>
  );
}
```

### Local Field Read Functions (Type Policies)

Local field `read` functions are defined in entity-level type policies. You can use reactive variables inside these `read` functions, along with other calculations or derived values:

```typescript
const cache = new InMemoryCache({
  typePolicies: {
    User: {
      fields: {
        // Simple local field from reactive variable
        isSelected: {
          read(_, { readField }) {
            const id = readField("id");
            return selectedUsersVar().includes(id);
          },
        },

        // Computed local field (derived value)
        displayName: {
          read(_, { readField }) {
            const name = readField("name");
            const email = readField("email");
            return name || email?.split("@")[0] || "Anonymous";
          },
        },
      },
    },
  },
});
```

## LocalState Resolvers

### Query-Level Local Resolvers

Query-level local fields can be defined using `LocalState` resolvers. **Note**: Do not read reactive variables inside LocalState resolvers - this is not a documented/tested feature. It might not behave as expected.

```typescript
import { LocalState } from "@apollo/client/local-state";

const client = new ApolloClient({
  cache: new InMemoryCache(),
  localState: new LocalState({
    resolvers: {
      Query: {
        // Read from localStorage
        theme: () => {
          if (typeof window !== "undefined") {
            return localStorage.getItem("theme") || "light";
          }
          return "light";
        },

        // Read from cache
        currentUser: (_, __, { cache }) => {
          const userId = localStorage.getItem("currentUserId");
          if (!userId) return null;
          return cache.readFragment({
            id: cache.identify({ __typename: "User", id: userId }),
            fragment: gql`
              fragment CurrentUser on User {
                id
                name
                email
              }
            `,
          });
        },

        // Compute value
        isOnline: () => {
          if (typeof navigator !== "undefined") {
            return navigator.onLine;
          }
          return true;
        },
      },
    },
  }),
});
```

### Using Local Query Fields

```tsx
const GET_AUTH_STATE = gql`
  query GetAuthState {
    isLoggedIn @client
    currentUser @client {
      id
      name
      email
    }
  }
`;

function AuthStatus() {
  const { data } = useQuery(GET_AUTH_STATE);

  if (!data?.isLoggedIn) {
    return <LoginButton />;
  }

  return <UserMenu user={data.currentUser} />;
}
```

## Combining Remote and Local State

### Mixing Remote and Local Fields

```tsx
const GET_PRODUCTS = gql`
  query GetProducts {
    products {
      id
      name
      price
      # Local fields
      quantity @client
      isInCart @client
    }
  }
`;

const cache = new InMemoryCache({
  typePolicies: {
    Product: {
      fields: {
        quantity: {
          read(_, { readField }) {
            const id = readField("id");
            const cartItem = cartItemsVar().find((item) => item.productId === id);
            return cartItem?.quantity ?? 0;
          },
        },

        isInCart: {
          read(_, { readField }) {
            const id = readField("id");
            return cartItemsVar().some((item) => item.productId === id);
          },
        },
      },
    },
  },
});
```

### Local Mutations

```tsx
import { LocalState } from "@apollo/client/local-state";

const client = new ApolloClient({
  cache: new InMemoryCache(),
  localState: new LocalState({
    resolvers: {
      Mutation: {
        addToCart: (_, { productId, quantity }, { cache }) => {
          // Read current cart from cache
          const { cart } = cache.readQuery({ query: GET_CART }) || { cart: [] };

          const existing = cart.find((item) => item.productId === productId);

          const updatedCart = existing
            ? cart.map((item) =>
                item.productId === productId ? { ...item, quantity: item.quantity + quantity } : item,
              )
            : [...cart, { productId, quantity, __typename: "CartItem" }];

          // Write updated cart back to cache
          cache.writeQuery({
            query: GET_CART,
            data: { cart: updatedCart },
          });

          return true;
        },
      },
    },
  }),
});

const ADD_TO_CART = gql`
  mutation AddToCart($productId: ID!, $quantity: Int!) {
    addToCart(productId: $productId, quantity: $quantity) @client
  }
`;
```

### Persisting Local State

```typescript
// Create a helper function to permanently subscribe to reactive variable changes, without creating memory leaks
function subscribeToVariable<T>(weakRef: WeakRef<ReactiveVar<T>>, listener: ReactiveListener<T>) {
  weakRef.deref()?.onNextChange((value) => {
    listener(value);
    subscribeToVariable(weakRef, listener);
  });
}

// Create reactive variable with persistence
const persistentCartVar = makeVar<CartItem[]>(
  typeof window !== "undefined" && localStorage.getItem("cart") ? JSON.parse(localStorage.getItem("cart")!) : [],
);

// Save to localStorage when reactive variable changes
subscribeToVariable(new WeakRef(persistentCartVar), (items) => {
  try {
    if (typeof window !== "undefined") {
      localStorage.setItem("cart", JSON.stringify(items));
    }
  } catch (error) {
    console.error("Failed to persist cart:", error);
  }
});
```

## useReactiveVar Hook

The `useReactiveVar` hook subscribes a component to reactive variable updates.

### Basic Usage

```tsx
import { useReactiveVar } from "@apollo/client/react";

function ThemeToggle() {
  const theme = useReactiveVar(themeVar);

  return <button onClick={() => themeVar(theme === "light" ? "dark" : "light")}>Current: {theme}</button>;
}
```

### With Derived State

```tsx
function CartSummary() {
  const cartItems = useReactiveVar(cartItemsVar);

  // Derived values are computed on each render
  const totalItems = cartItems.reduce((sum, item) => sum + item.quantity, 0);
  const totalPrice = cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0);

  return (
    <div>
      <p>Items: {totalItems}</p>
      <p>Total: ${totalPrice.toFixed(2)}</p>
    </div>
  );
}
```

### Multiple Reactive Variables

```tsx
function AppLayout() {
  const theme = useReactiveVar(themeVar);
  const sidebarOpen = useReactiveVar(sidebarOpenVar);
  const isLoggedIn = useReactiveVar(isLoggedInVar);

  return (
    <div className={`app ${theme}`}>
      {isLoggedIn && sidebarOpen && <Sidebar />}
      <main>
        <Outlet />
      </main>
    </div>
  );
}
```

```

### references/error-handling.md

```markdown
# Error Handling Reference (Apollo Client 4.x)

Note that Apollo Client 4.x handles errors differently than Apollo Client 3.x.
This reference documents the updated error handling mechanisms, error types, and best practices for managing errors in your Apollo Client applications.
For older Apollo Client 3.x error handling documentation, see [Apollo Client 3.x Error Handling](https://www.apollographql.com/docs/react/v3/data/error-handling).

## Table of Contents

- [Understanding Errors](#understanding-errors)
- [Error Types](#error-types)
- [Identifying Error Types](#identifying-error-types)
- [GraphQL Error Policies](#graphql-error-policies)
- [Error Links](#error-links)
- [Retry Logic](#retry-logic)
- [Error Boundaries](#error-boundaries)

## Understanding Errors

Errors in Apollo Client fall into two main categories: **GraphQL errors** and **network errors**. Each category has specific error classes that provide detailed information about what went wrong.

### GraphQL Errors

GraphQL errors are related to server-side execution of a GraphQL operation:

- **Syntax errors** (e.g., malformed query)
- **Validation errors** (e.g., query includes a non-existent schema field)
- **Resolver errors** (e.g., error while populating a query field)

If a syntax or validation error occurs, the server doesn't execute the operation. If resolver errors occur, the server can still return partial data.

Example server response with GraphQL error:

```json
{
  "errors": [
    {
      "message": "Cannot query field \"nonexistentField\" on type \"Query\".",
      "locations": [{ "line": 2, "column": 3 }],
      "extensions": {
        "code": "GRAPHQL_VALIDATION_FAILED"
      }
    }
  ],
  "data": null
}
```

In Apollo Client 4.x, GraphQL errors are represented by the [`CombinedGraphQLErrors`](https://apollographql.com/docs/react/api/errors/CombinedGraphQLErrors) error type.

### Network Errors

Network errors occur when attempting to communicate with your GraphQL server:

- `4xx` or `5xx` HTTP response status codes
- Network unavailability
- JSON parsing failures
- Custom errors from Apollo Link request handlers

Network errors might be represented by special error types, but if an api such as the `fetch` API throws a native error (e.g., `TypeError`), Apollo Client will pass it through as-is.
Thrown values that don't fulfill the standard `ErrorLike` interface are wrapped in the [`UnconventionalError`](https://apollographql.com/docs/react/api/errors/UnconventionalError) class, which fulfills the `ErrorLike` interface. As such, you can expect any error returned by Apollo Client to fulfill the `ErrorLike` interface.

```ts
export interface ErrorLike {
  message: string;
  name: string;
  stack?: string;
}
```

## Error Types

Apollo Client 4.x provides specific error classes for different error scenarios:

### CombinedGraphQLErrors

Represents GraphQL errors returned by the server. Most common error type in applications.

```tsx
import { CombinedGraphQLErrors } from "@apollo/client/errors";

function UserProfile({ userId }: { userId: string }) {
  const { data, error } = useQuery(GET_USER, {
    variables: { id: userId },
  });

  // no need to check for nullishness of error, CombinedGraphQLErrors.is handles that
  if (CombinedGraphQLErrors.is(error)) {
    // Handle GraphQL errors
    return (
      <div>
        {error.graphQLErrors.map((err, i) => (
          <p key={i}>GraphQL Error: {err.message}</p>
        ))}
      </div>
    );
  }

  return data ? <Profile user={data.user} /> : null;
}
```

### CombinedProtocolErrors

Represents fatal transport-level errors during multipart HTTP subscription execution.

### ServerError

Occurs when the server responds with a non-200 HTTP status code.

```tsx
import { ServerError } from "@apollo/client/errors";

if (ServerError.is(error)) {
  console.error("Server error:", error.statusCode, error.result);
}
```

### ServerParseError

Occurs when the server response cannot be parsed as valid JSON.

```tsx
import { ServerParseError } from "@apollo/client/errors";

if (ServerParseError.is(error)) {
  console.error("Invalid JSON response:", error.bodyText);
}
```

### LocalStateError

Represents errors in local state configuration or execution.

### UnconventionalError

Wraps non-standard errors (e.g., thrown symbols or objects) to ensure consistent error handling.

## Identifying Error Types

Every Apollo Client error class provides a static `is` method that reliably determines whether an error is of that specific type. This is more robust than `instanceof` because it avoids false positives/negatives.

```ts
import {
  CombinedGraphQLErrors,
  CombinedProtocolErrors,
  LocalStateError,
  ServerError,
  ServerParseError,
  UnconventionalError,
  ErrorLike,
} from "@apollo/client/errors";

// Anything returned in the `error` field of Apollo Client hooks or methods is of type `ErrorLike` or `undefined`.
function handleError(error?: ErrorLike) {
  if (CombinedGraphQLErrors.is(error)) {
    // Handle GraphQL errors
    console.error("GraphQL errors:", error.graphQLErrors);
  } else if (CombinedProtocolErrors.is(error)) {
    // Handle multipart subscription protocol errors
  } else if (LocalStateError.is(error)) {
    // Handle errors thrown by the LocalState class
  } else if (ServerError.is(error)) {
    // Handle server HTTP errors
    console.error("Server error:", error.statusCode);
  } else if (ServerParseError.is(error)) {
    // Handle JSON parse errors
  } else if (UnconventionalError.is(error)) {
    // Handle errors thrown by irregular types
  } else if (error) {
    // Handle other errors
  }
}
```

## GraphQL Error Policies

If a GraphQL operation produces errors, the server's response might still include partial data:

```json
{
  "data": {
    "getInt": 12,
    "getString": null
  },
  "errors": [
    {
      "message": "Failed to get string!"
    }
  ]
}
```

By default, Apollo Client throws away partial data and populates the `error` field. You can use partial results by defining an **error policy**:

| Policy   | Description                                                                                                                                                                  |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `none`   | (Default) If the response includes errors, they are returned in `error` and response `data` is set to `undefined` even if the server returns `data`.                         |
| `ignore` | Errors are ignored (`error` is not populated), and any returned `data` is cached and rendered as if no errors occurred. `data` may be `undefined` if a network error occurs. |
| `all`    | Both `data` and `error` are populated and any returned `data` is cached, enabling you to render both partial results and error information.                                  |

### Setting an Error Policy

```tsx
const MY_QUERY = gql`
  query WillFail {
    badField # This field's resolver produces an error
    goodField # This field is populated successfully
  }
`;

function ShowingSomeErrors() {
  const { loading, error, data } = useQuery(MY_QUERY, { errorPolicy: "all" });

  if (loading) return <span>loading...</span>;

  return (
    <div>
      <h2>Good: {data?.goodField}</h2>
      {error && <pre>Bad: {error.message}</pre>}
    </div>
  );
}
```

### Avoid setting a Global Error Policy

While it is possible to set a global error policy using `defaultOptions`, in practice this is discouraged as it can lead to unexpected behavior and type safety issues. The return types of the TypeScript hooks may change depending on the `errorPolicy` passed into the hook, and this can conceptually not take global `defaultOptions` error policies into account. As such, it is best to set the `errorPolicy` per operation as needed.

## Error Links

The `ErrorLink` can be used to e.g. log error globally or perform specific side effects based on errors happening.

An `ErrorLink` can't be used to swallow errors fully, but it can be used to retry an operation after handling an error, in which case the error wouldn't propagate. Otherwise, the most common use for `ErrorLink` is logging.

```ts
import { ErrorLink } from "@apollo/client/link/error";

const errorLink = new ErrorLink(({ error, operation, forward }) => {
  if (someCondition(error)) {
    // Retry the request, returning the new observable
    return forward(operation);
  }

  // Log the error for any unhandled GraphQL errors or network errors.
  console.log(`[Error]: ${error.message}`);

  // If nothing is returned from the error handler callback, the error will be
  // emitted from the link chain as normal.
});
```

### Retry Link

Alternatively, you can use the `RetryLink` from `@apollo/client/link/retry` to implement retry logic for failed operations.

```typescript
import { RetryLink } from "@apollo/client/link/retry";

const retryLink = new RetryLink({
  delay: {
    initial: 300,
    max: Infinity,
    jitter: true,
  },
  attempts: {
    max: 5,
    retryIf: (error, operation) => {
      // Retry on network errors
      return !!error && operation.operationName !== "SensitiveOperation";
    },
  },
});

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: from([retryLink, errorLink, httpLink]),
});
```

### Custom Retry Logic

```typescript
const retryLink = new RetryLink({
  attempts: (count, operation, error) => {
    // Don't retry mutations
    if (operation.query.definitions.some((def) => def.kind === "OperationDefinition" && def.operation === "mutation")) {
      return false;
    }

    // Retry up to 3 times on network errors
    return count < 3 && !!error;
  },
  delay: (count) => {
    // Exponential backoff
    return Math.min(1000 * Math.pow(2, count), 30000);
  },
});
```

## Error Boundaries

When using suspenseful hooks, you should use React Error Boundaries for graceful error handling.

### Non-suspense per-Component Error Handling

```tsx
function SafeUserList() {
  const { data, error, loading, refetch } = useQuery(GET_USERS, {
    errorPolicy: "all",
    notifyOnNetworkStatusChange: true,
  });

  // Handle network errors
  if (error?.networkError) {
    return (
      <Alert severity="error">
        <AlertTitle>Connection Error</AlertTitle>
        Failed to load users. Please check your internet connection.
        <Button onClick={() => refetch()}>Retry</Button>
      </Alert>
    );
  }

  // Handle GraphQL errors but still show available data
  return (
    <div>
      {error?.graphQLErrors && (
        <Alert severity="warning">Some data may be incomplete: {error.graphQLErrors[0].message}</Alert>
      )}

      {loading && <LinearProgress />}

      {data?.users && <UserList users={data.users} />}
    </div>
  );
}
```

```

### references/troubleshooting.md

```markdown
# Troubleshooting Reference

## Table of Contents

- [Setup Issues](#setup-issues)
- [Cache Issues](#cache-issues)
- [TypeScript Issues](#typescript-issues)
- [Performance Issues](#performance-issues)
- [DevTools Usage](#devtools-usage)
- [Common Error Messages](#common-error-messages)

## Setup Issues

### Provider Not Found

**Error:** `Could not find "client" in the context or passed in as an option`

**Cause:** Component is not wrapped with `ApolloProvider`.

**Solution:**

```tsx
// Ensure ApolloProvider wraps your app
import { ApolloProvider } from "@apollo/client";

function App() {
  return (
    <ApolloProvider client={client}>
      <YourApp />
    </ApolloProvider>
  );
}
```

### Multiple Apollo Clients

**Problem:** Unintended cache isolation or conflicting states.

**Solution:** Use a single client instance or explicitly manage multiple clients:

```tsx
// Single client (recommended)
const client = new ApolloClient({
  /* ... */
});

export function App() {
  return (
    <ApolloProvider client={client}>
      <Router />
    </ApolloProvider>
  );
}

// Multiple clients (rare use case)
const publicClient = new ApolloClient({
  uri: "/public/graphql",
  cache: new InMemoryCache(),
});
const adminClient = new ApolloClient({
  uri: "/admin/graphql",
  cache: new InMemoryCache(),
});

function AdminSection() {
  return (
    <ApolloProvider client={adminClient}>
      <AdminDashboard />
    </ApolloProvider>
  );
}
```

### Client Created in Component

**Problem:** New client on every render causes cache loss.

**Solution:** Create client outside component or use a ref pattern:

```tsx
// Bad - new client on every render
function App() {
  const client = new ApolloClient({
    /* ... */
  }); // Don't do this!
  return <ApolloProvider client={client}>...</ApolloProvider>;
}

// Module-level client definition
// Okay if there is a 100% guarantee this application will never use SSR
const client = new ApolloClient({
  /* ... */
});
function App() {
  return <ApolloProvider client={client}>...</ApolloProvider>;
}

// Good - store Apollo Client in a ref that is initialized once
function useApolloClient(makeApolloClient: () => ApolloClient): ApolloClient {
  const storeRef = useRef<ApolloClient | null>(null);
  if (!storeRef.current) {
    storeRef.current = makeApolloClient();
  }
  return storeRef.current;
}

// Better - singleton global in non-SSR environments to survive unmounts
const singleton = Symbol.for("ApolloClientSingleton");
declare global {
  interface Window {
    [singleton]?: ApolloClient;
  }
}

function useApolloClient(makeApolloClient: () => ApolloClient): ApolloClient {
  const storeRef = useRef<ApolloClient | null>(null);
  if (!storeRef.current) {
    if (typeof window === "undefined") {
      storeRef.current = makeApolloClient();
    } else {
      window[singleton] ??= makeApolloClient();
      storeRef.current = window[singleton];
    }
  }
  return storeRef.current;
}
// Note: this second option might need manual removal between tests
```

## Cache Issues

### Stale Data Not Updating

**Problem:** UI doesn't reflect mutations or other updates.

**Solution 1:** Verify cache key identification:

```typescript
const cache = new InMemoryCache({
  typePolicies: {
    // Ensure proper identification
    Product: {
      keyFields: ["id"], // or ['sku'] if no id field
    },
  },
});
```

**Solution 2:** Update cache after mutations:

```tsx
const [deleteProduct] = useMutation(DELETE_PRODUCT, {
  update: (cache, { data }) => {
    cache.evict({ id: cache.identify(data.deleteProduct) });
    cache.gc();
  },
});
```

**Solution 3:** Use appropriate fetch policy:

```tsx
const { data } = useQuery(GET_PRODUCTS, {
  fetchPolicy: "cache-and-network", // Always fetch fresh data
});
```

### Missing Cache Updates After Mutation

**Problem:** New items don't appear in lists after creation.

**Solution:** Manually update the cache:

```tsx
const [createProduct] = useMutation(CREATE_PRODUCT, {
  update: (cache, { data }) => {
    const existing = cache.readQuery<{ products: Product[] }>({
      query: GET_PRODUCTS,
    });

    cache.writeQuery({
      query: GET_PRODUCTS,
      data: {
        products: [...(existing?.products ?? []), data.createProduct],
      },
    });
  },
});
```

### Pagination Cache Issues

**Problem:** Paginated data not merging correctly.

**Solution:** Configure proper type policies:

```typescript
const cache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        products: {
          keyArgs: ["category"], // Only category creates new cache entries
          merge(existing = [], incoming) {
            return [...existing, ...incoming];
          },
        },
      },
    },
  },
});
```

### Cache Normalization Problems

**Problem:** Objects with same ID showing different data in different queries.

**Debug:** Check cache contents:

```typescript
// In DevTools console or component
console.log(client.cache.extract());
```

**Solution:** Ensure consistent `__typename` and `id` fields:

```graphql
query GetUsers {
  users {
    id # Always include id
    name
  }
}
```

## TypeScript Issues

### Type Generation Setup

**Problem:** No type safety for GraphQL operations.

**Solution:** Set up GraphQL Code Generator with the [recommended starter configuration](https://www.apollographql.com/docs/react/development-testing/graphql-codegen#recommended-starter-configuration), as described in the [Skill](../SKILL.md).

### Using Generated Types

```tsx
import { useQuery } from "@apollo/client/react";
import { GetUsersDocument, GetUsersQuery } from "./generated/graphql";

function UserList() {
  // Fully typed without manual type annotations
  const { data, loading, error } = useQuery(GetUsersDocument);

  // data.users is automatically typed as GetUsersQuery['users']
  return (
    <ul>
      {data?.users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
```

## Performance Issues

### Over-Fetching

**Problem:** Fetching more data than needed.

**Solution:** Select only required fields:

```graphql
# Bad - fetching everything
query GetUsers {
  users {
    id
    name
    email
    profile { ... }
    posts { ... }
    friends { ... }
  }
}

# Good - fetch what's needed
query GetUserNames {
  users {
    id
    name
  }
}
```

### N+1 Queries

**Problem:** Multiple network requests for related data.

**Solution:** Structure queries to batch requests. Best practice: use query colocation and compose queries from fragments defined on child components.

```graphql
# Bad - separate queries
query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
  }
}
query GetUserPosts($userId: ID!) {
  posts(userId: $userId) {
    id
    title
  }
}

# Good - single query
query GetUserWithPosts($id: ID!) {
  user(id: $id) {
    id
    name
    posts {
      id
      title
    }
  }
}
```

### Unnecessary Re-Renders

**Problem:** Components re-render when unrelated cache data changes.

**Solution:** Use `useFragment` and data masking for selective field reading. If that is not possible, `useQuery` with `@nonreactive` directives might be an alternative.

```tsx
// Prefer useFragment with data masking
const { data } = useFragment({
  fragment: USER_FRAGMENT,
  from: { __typename: "User", id },
});

// Alternative: use @nonreactive directive
const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      # This field won't trigger re-renders when it changes
      metadata @nonreactive {
        lastSeen
        preferences
      }
    }
  }
`;

const { data } = useQuery(GET_USER, {
  variables: { id },
});
```

### Cache Misses

**Debug:** Use Apollo DevTools to inspect cache.

```typescript
const client = new ApolloClient({
  cache: new InMemoryCache(),
  // DevTools are enabled by default in development
  // Only configure this when you need to enable them in production
  devtools: {
    enabled: true,
  },
});
```

## DevTools Usage

### Installing Apollo DevTools

Install the browser extension:

- [Chrome](https://chrome.google.com/webstore/detail/apollo-client-devtools/jdkknkkbebbapilgoeccciglkfbmbnfm)
- [Firefox](https://addons.mozilla.org/en-US/firefox/addon/apollo-developer-tools/)

### Enabling DevTools

DevTools are enabled by default in development. Only configure this setting if you need to enable them in production:

```typescript
const client = new ApolloClient({
  cache: new InMemoryCache(),
  devtools: {
    enabled: true, // Set to true to enable in production
  },
});
```

### DevTools Features

1. **Cache Inspector**: View normalized cache contents
2. **Queries**: See active queries and their states
3. **Mutations**: Track mutation history
4. **Explorer**: Build and test queries against your schema
5. **Memoization Limits**: Monitor and track cache memoization
6. **Cache Writes**: Track all writes to the cache

### Debugging Cache

```typescript
// Log cache contents
console.log(JSON.stringify(client.cache.extract(), null, 2));

// Check specific object using cache.identify
console.log(
  client.cache.readFragment({
    id: cache.identify({ __typename: "User", id: 1 }),
    fragment: gql`
      fragment _ on User {
        id
        name
        email
      }
    `,
  }),
);
```

## Common Error Messages

### "Missing field 'X' in {...}"

**Cause:** Query doesn't include required field for cache normalization.

**Solution:** Include `id` and `__typename`:

```graphql
query GetUsers {
  users {
    id # Required for caching
    __typename # Usually added automatically
    name
  }
}
```

**Additional advice**: Read the full error message thoroughly.

### "Store reset while query was in flight"

**Cause:** `client.resetStore()` called during active queries.

**Solution:** Wait for queries to complete or use `clearStore()`:

```typescript
// Option 1: Clear without refetching
await client.clearStore();

// Option 2: Reset and refetch active queries
await client.resetStore();
```

### "Invariant Violation: X"

**Cause:** Various configuration or usage errors.

**Common fixes:**

- Ensure `ApolloProvider` wraps the component tree
- Check that `gql` tagged templates are valid GraphQL
- Verify cache configuration matches your schema

### "Cannot read property 'X' of undefined"

**Cause:** Accessing data before query completes.

**Solution:** Check `dataState` for proper type narrowing:

```tsx
const { data, dataState } = useQuery(GET_USER);

// dataState can be "complete", "partial", "streaming", or "empty"
// It describes the completeness of the data, not a loading state
if (dataState === "empty") return <Spinner />;

// Now data is guaranteed to exist
return <div>{data.user.name}</div>;

// Or use optional chaining
return <div>{data?.user?.name}</div>;
```

```