Subchapter 1.2
references/error-handling.mdMarkdown11 KBView on GitHub
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 .
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 are related to server-side execution of a GraphQL operation:
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:
{
"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 (opens in a new tab) error type.
Network errors occur when attempting to communicate with your GraphQL server:
4xx or 5xx HTTP response status codesNetwork 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 (opens in a new tab) class, which fulfills the ErrorLike interface. As such, you can expect any error returned by Apollo Client to fulfill the ErrorLike interface.
export interface ErrorLike {
message: string;
name: string;
stack?: string;
}Apollo Client 4.x provides specific error classes for different error scenarios:
Represents GraphQL errors returned by the server. Most common error type in applications.
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;
}Represents fatal transport-level errors during multipart HTTP subscription execution.
Occurs when the server responds with a non-200 HTTP status code.
import { ServerError } from "@apollo/client/errors";
if (ServerError.is(error)) {
console.error("Server error:", error.statusCode, error.result);
}Occurs when the server response cannot be parsed as valid JSON.
import { ServerParseError } from "@apollo/client/errors";
if (ServerParseError.is(error)) {
console.error("Invalid JSON response:", error.bodyText);
}Represents errors in local state configuration or execution.
Wraps non-standard errors (e.g., thrown symbols or objects) to ensure consistent error handling.
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.
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
}
}If a GraphQL operation produces errors, the server’s response might still include partial data:
{
"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. |
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>
);
}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.
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.
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.
});Alternatively, you can use the RetryLink from @apollo/client/link/retry to implement retry logic for failed operations.
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]),
});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);
},
});When using suspenseful hooks, you should use React Error Boundaries for graceful error handling.
import {
CombinedGraphQLErrors,
ServerError,
ServerParseError,
} from "@apollo/client/errors";
function SafeUserList() {
const { data, error, loading, refetch } = useQuery(GET_USERS, {
errorPolicy: "all",
notifyOnNetworkStatusChange: true,
});
// Handle network errors
if (ServerError.is(error) || ServerParseError.is(error)) {
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>
{CombinedGraphQLErrors.is(error) && (
<Alert severity="warning">
Some data may be incomplete: {error.graphQLErrors[0].message}
</Alert>
)}
{loading && <LinearProgress />}
{data?.users && <UserList users={data.users} />}
</div>
);
}