apollographql / apollographql/apollo-client-integrations

Why is observer.error not handled in getClient or useSuspenseQuery?

Open
#259 3 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.