Skip to content

Commit

Permalink
Always throw network errors in useSupenseQuery regardless of the er…
Browse files Browse the repository at this point in the history
…ror policy (#10401)

* Update tests to ensure network errors are always thrown

* Always throw network errors from useSuspenseQuery

* Add changeset
  • Loading branch information
jerelmiller authored Jan 4, 2023
1 parent 98cacc9 commit 3e5b41a
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 33 deletions.
5 changes: 5 additions & 0 deletions .changeset/short-bikes-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@apollo/client': patch
---

Always throw network errors in `useSuspenseQuery` regardless of the set `errorPolicy`.
57 changes: 28 additions & 29 deletions src/react/hooks/__tests__/useSuspenseQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2097,30 +2097,32 @@ describe('useSuspenseQuery', () => {
consoleSpy.mockRestore();
});

it('does not throw or return network errors when errorPolicy is set to "ignore"', async () => {
const { query, mocks } = useErrorCase({
networkError: new Error('Could not fetch'),
});
it('throws network errors when errorPolicy is set to "ignore"', async () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
const networkError = new Error('Could not fetch');

const { result, renders } = renderSuspenseHook(
const { query, mocks } = useErrorCase({ networkError });

const { renders } = renderSuspenseHook(
() => useSuspenseQuery(query, { errorPolicy: 'ignore' }),
{ mocks }
);

await waitFor(() => {
expect(result.current).toMatchObject({
data: undefined,
error: undefined,
});
expect(renders.errorCount).toBe(1);
});

expect(renders.errorCount).toBe(0);
expect(renders.errors).toEqual([]);
expect(renders.count).toBe(2);
expect(renders.errors.length).toBe(1);
expect(renders.suspenseCount).toBe(1);
expect(renders.frames).toMatchObject([
{ data: undefined, error: undefined },
]);
expect(renders.frames).toEqual([]);

const [error] = renders.errors as ApolloError[];

expect(error).toBeInstanceOf(ApolloError);
expect(error!.networkError).toEqual(networkError);
expect(error!.graphQLErrors).toEqual([]);

consoleSpy.mockRestore();
});

it('does not throw or return graphql errors when errorPolicy is set to "ignore"', async () => {
Expand Down Expand Up @@ -2149,7 +2151,7 @@ describe('useSuspenseQuery', () => {
]);
});

it('returns partial data results and discards errors when errorPolicy is set to "ignore"', async () => {
it('returns partial data results and discards GraphQL errors when errorPolicy is set to "ignore"', async () => {
const { query, mocks } = useErrorCase({
data: { currentUser: { id: '1', name: null } },
graphQLErrors: [new GraphQLError('`name` could not be found')],
Expand Down Expand Up @@ -2200,36 +2202,33 @@ describe('useSuspenseQuery', () => {
]);
});

it('does not throw and returns network errors when errorPolicy is set to "all"', async () => {
it('throws network errors when errorPolicy is set to "all"', async () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();

const networkError = new Error('Could not fetch');

const { query, mocks } = useErrorCase({ networkError });

const { result, renders } = renderSuspenseHook(
const { renders } = renderSuspenseHook(
() => useSuspenseQuery(query, { errorPolicy: 'all' }),
{ mocks }
);

await waitFor(() => {
expect(result.current).toMatchObject({
data: undefined,
error: new ApolloError({ networkError }),
});
expect(renders.errorCount).toBe(1);
});

expect(renders.errorCount).toBe(0);
expect(renders.errors).toEqual([]);
expect(renders.count).toBe(2);
expect(renders.errors.length).toBe(1);
expect(renders.suspenseCount).toBe(1);
expect(renders.frames).toMatchObject([
{ data: undefined, error: new ApolloError({ networkError }) },
]);
expect(renders.frames).toEqual([]);

const { error } = result.current;
const [error] = renders.errors as ApolloError[];

expect(error).toBeInstanceOf(ApolloError);
expect(error!.networkError).toEqual(networkError);
expect(error!.graphQLErrors).toEqual([]);

consoleSpy.mockRestore();
});

it('does not throw and returns graphql errors when errorPolicy is set to "all"', async () => {
Expand Down
11 changes: 7 additions & 4 deletions src/react/hooks/useSuspenseQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,17 @@ export function useSuspenseQuery_experimental<
const hasPartialResult = result.data && result.partial;
const usePartialResult = returnPartialData && hasPartialResult;

if (
result.error &&
errorPolicy === 'none' &&
const allowsThrownErrors =
// If we've got a deferred query that errors on an incremental chunk, we
// will have a partial result before the error is collected. We do not want
// to throw errors that have been returned from incremental chunks. Instead
// we offload those errors to the `error` property.
(!deferred || !hasPartialResult)
errorPolicy === 'none' && (!deferred || !hasPartialResult);

if (
result.error &&
// Always throw network errors regardless of the error policy
(result.error.networkError || allowsThrownErrors)
) {
throw result.error;
}
Expand Down

0 comments on commit 3e5b41a

Please sign in to comment.