Subchapter 1.12
references/troubleshooting.mdMarkdown10 KBView on GitHub
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>
);
}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>
);
}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 testsProblem: 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
});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],
},
});
},
});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];
},
},
},
},
},
});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
}
}Problem: No type safety for GraphQL operations.
Solution: Set up GraphQL Code Generator with the recommended starter configuration (opens in a new tab), as described in the Skill.
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>
);
}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
}
}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
}
}
}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 },
});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,
},
});Install the browser extension:
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
},
});// 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
}
`,
})
);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.
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();Cause: Various configuration or usage errors.
Common fixes:
ApolloProvider wraps the component treegql tagged templates are valid GraphQLCause: 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>;