apollographql / apollographql/apollo-client-integrations
Why is observer.error not handled in getClient or useSuspenseQuery?
- Dominant language
- TypeScript
- Stars
- 556
- Forks
- 53
- PR merge metrics
- No merged PRs in 30d
Description
Hello!
I have this code:
```tsx
import { onError } from '@apollo/client/link/error';
import type { FetchResult } from '@apollo/client/link/core/types';
import {
type ApolloClient,
type NormalizedCacheObject,
Observable,
} from '@apollo/client';
import { type NextSSRApolloClient } from '@apollo/experimental-nextjs-app-support/ssr';
import {
RefreshTokensDocument,
type RefreshTokensMutation,
} from './refreshTokens.generated';
export const createAuthErrorLink = (
getClient:
| (() => ApolloClient)
| (() => NextSSRApolloClient)
) =>
onError(({ graphQLErrors, networkError, operation, forward }) => {
const isAuthError = graphQLErrors?.find(
(error) => error.extensions.code === 'UNAUTHENTICATED'
);
if (isAuthError) {
// Ignore 401 error for a refresh request.
if (operation.operationName === 'RefreshTokens') return;
const observable = new Observable((observer) => {
const refreshTokens = async () => {
try {
const { data } = await getClient().mutate({
mutation: RefreshTokensDocument,
});
if (!data?.refreshTokens) {
throw new Error('Refresh Tokens Error');
}
const { accessToken, refreshToken } = data.refreshTokens;
const cookie = [
`accessToken=${accessToken};`,
`refreshToken=${refreshToken};`,
];
const oldHeaders = operation.getContext().headers as object;
operation.setContext({
headers: {
...oldHeaders,
cookie,
},
});
forward(operation).subscribe(observer);
} catch (error) {
observer.error(error);
}
};
void refreshTokens();
});
return observable;
}
if (networkError) console.error(`[Network error]: ${networkError}`);
});
```
This is my `getClient` definition:
```tsx
import { registerApolloClient } from '@apollo/experimental-nextjs-app-support/rsc';
import {
ApolloLink,
HttpLink,
from,
InMemoryCache,
ApolloClient,
} from '@apollo/client';
import { cookies } from 'next/headers';
import { createAuthErrorLink } from './create-auth-error-link';
const cookieLink = new ApolloLink((operation, forward) => {
const cookieHeader = cookies();
const oldHeaders = operation.getContext().headers as object;
operation.setContext({
headers: {
...oldHeaders,
cookie: cookieHeader,
},
});
return forward(operation);
});
export const { getClient } = registerApolloClient(() => {
const httpLink = new HttpLink({
uri: 'http://127.0.0.1:3000/graphql',
credentials: 'include',
});
const authErrorLink = createAuthErrorLink(getClient);
return new ApolloClient({
cache: new InMemoryCache(),
link: from([cookieLink, authErrorLink, httpLink]),
});
});
```
And when I use `useQuery` from '@apollo/client', everything works correctly. However, when I use `getClient` or `useSuspenseQuery`, for example:
```tsx
const { data, error } = await getClient().query({
query: GetCurrentUserDocument,
});
```
I get an unhandled error in Next.js when calling `observer.error(error);`, and I can't retrieve the error just from the `getClient` result. How can I fix that?
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the createAuthErrorLink Observable error path and the registerApolloClient getClient entry point shown in the issue. Compare error propagation for useQuery, getClient().query, and useSuspenseQuery, then reproduce the Next.js unhandled error. Done means authentication refresh failures are exposed through the documented query result or error mechanism without an unhandled error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- graphql, next.js, typescript
- Domain
- api, authentication, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100