All skills
apollographql avatar

/apollo-client

@cb48e40 official
by Apollo GraphQLapollographql/skills115 stars
13

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.

Use this Skill: https://skilld.dev/gh/apollographql/skills/apollo-client

This session only. Nothing lands on disk.

referencestroubleshooting.md

≈2.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Troubleshooting Reference

Table of Contents

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:

// 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:

// 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:

// 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:

const cache = new InMemoryCache({
  typePolicies: {
    // Ensure proper identification
    Product: {
      keyFields: ["id"], // or ['sku'] if no id field
    },
  },
});

Solution 2: Update cache after mutations:

const [deleteProduct] = useMutation(DELETE_PRODUCT, {
  update: (cache, { data }) => {
    cache.evict({ id: cache.identify(data.deleteProduct) });
    cache.gc();
  },
});

Solution 3: Use appropriate fetch policy:

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:

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:

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:

// In DevTools console or component
console.log(client.cache.extract());

Solution: Ensure consistent __typename and id fields:

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, as described in the Skill.

Using Generated Types

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:

# 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.

# 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.

// 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.

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:

Enabling DevTools

DevTools are enabled by default in development. Only configure this setting if you need to enable them in production:

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

// 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:

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():

// 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:

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>;

Source: SKILL.md on GitHub

1 warning16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill is a comprehensive reference and integration guide for Apollo Client 4.x in React applications. No security vulnerabilities, malicious code, or adversarial patterns were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    4/14 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at cb48e40. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 5 months ago
What it can do
Runs commands
metadata
{
  "author": "apollographql",
  "version": "1.0.0"
}
All 1 allowed tools
Bash(npm:*) Bash(npx:*) Bash(node:*) Read Write Edit Glob Grep
Other metadata
compatibility
React 18+, React 19 (Suspense/RSC). Works with Next.js, Vite, CRA, and other React frameworks.
  • React
  • TypeScript
  • apollo-client
  • graphql
  • state-management
  • caching
  • queries
  • mutations
  • suspense

README badge

README badge for apollographql/skills/apollo-client

Guides for building React applications with Apollo Client 4.x, covering queries, mutations, caching, local state, and Suspense integration. Targets React 18+, React 19, Next.js, Vite, and other React frameworks with patterns for client-side apps, server-side rendering, and modern data fetching.

Generated from the current SKILL.md.

Does this skill work with React Server Components and Next.js App Router?
Yes. The skill includes integration guides for Next.js App Router with React Server Components, and supports React 19 with Suspense.
What Apollo Client version does this skill target?
Apollo Client 4.x specifically. The skill does not cover v3 or earlier patterns.
Does this skill cover TypeScript code generation for GraphQL operations?
Yes. The skill includes a reference section on TypeScript Code Generator setup for type-safe operations.
Can I use this skill to configure caching and optimize performance?
Yes. The skill covers InMemoryCache configuration, typePolicies, fetchPolicy strategies, and performance patterns including @defer and @stream.
What frameworks does this skill support beyond client-side React?
It includes integration guides for Next.js App Router, React Router 7 with streaming SSR, and TanStack Start, in addition to client-side apps built with Vite or Create React App.

Generated from the current SKILL.md. These answers refresh after source changes.