apollo-client-nextjs
apollo-client-nextjs copied to clipboard
Why is observer.error not handled in getClient or useSuspenseQuery?
Hello! I have this code:
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<unknown>)
| (() => NextSSRApolloClient<NormalizedCacheObject>)
) =>
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<FetchResult>((observer) => {
const refreshTokens = async () => {
try {
const { data } = await getClient().mutate<RefreshTokensMutation>({
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:
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:
const { data, error } = await getClient().query<GetCurrentUserQuery>({
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?
In the useSuspenseQuery
case, you should use an error boundary - and I believe in the await getClient().query
case you can either use a try..catch
block, or also an error boundary.
Yes, I can handle this error with a try-catch
, but can I do something to get it just from this error const { data, error } = await getClient().query
?
You could set the errorPolicy
in your query
call options - the default is none
, which will throw
, but you could also set errorPolicy: "all"
which would make it accessible the way you want to here.