@apollo/client
Version:
A fully-featured caching GraphQL client.
488 lines (377 loc) • 10.8 kB
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>;
```