diff --git a/CHANGELOG.md b/CHANGELOG.md
index f3f056dd3ef..406e395a1e6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,9 @@
- During server-side rendering, allow initial `useQuery` calls to return final `{ loading: false, data }` results when the cache already contains the necessary data.
[@benjamn](https://github.com/benjamn) in [#7983](https://github.com/apollographql/apollo-client/pull/7983)
+- Prevent `undefined` mutation result in useMutation
+ [@jcreighton](https://github.com/jcreighton) in [#8018](https://github.com/apollographql/apollo-client/pull/8018)
+
## Apollo Client 3.3.14
### Improvements
diff --git a/src/react/data/MutationData.ts b/src/react/data/MutationData.ts
index 209cd8be6ac..e4e2bcd6c20 100644
--- a/src/react/data/MutationData.ts
+++ b/src/react/data/MutationData.ts
@@ -74,8 +74,17 @@ export class MutationData<
return response;
})
.catch((error: ApolloError) => {
+ const { onError } = this.getOptions();
this.onMutationError(error, mutationId);
- if (!this.getOptions().onError) throw error;
+ if (onError) {
+ onError(error);
+ return {
+ data: undefined,
+ errors: error,
+ };
+ } else {
+ throw error;
+ }
});
};
@@ -128,8 +137,6 @@ export class MutationData<
}
private onMutationError(error: ApolloError, mutationId: number) {
- const { onError } = this.getOptions();
-
if (this.isMostRecentMutation(mutationId)) {
this.updateResult({
loading: false,
@@ -138,10 +145,6 @@ export class MutationData<
called: true
});
}
-
- if (onError) {
- onError(error);
- }
}
private generateNewMutationId(): number {
@@ -152,13 +155,14 @@ export class MutationData<
return this.mostRecentMutationId === mutationId;
}
- private updateResult(result: MutationResultWithoutClient) {
+ private updateResult(result: MutationResultWithoutClient): MutationResultWithoutClient | undefined {
if (
this.isMounted &&
(!this.previousResult || !equal(this.previousResult, result))
) {
this.setResult(result);
this.previousResult = result;
+ return result;
}
}
}
diff --git a/src/react/hooks/__tests__/useMutation.test.tsx b/src/react/hooks/__tests__/useMutation.test.tsx
index de541810bb7..19985873fa5 100644
--- a/src/react/hooks/__tests__/useMutation.test.tsx
+++ b/src/react/hooks/__tests__/useMutation.test.tsx
@@ -252,6 +252,58 @@ describe('useMutation Hook', () => {
});
describe('mutate function upon error', () => {
+ itAsync('resolves with the resulting data and errors', async (resolve, reject) => {
+ const variables = {
+ description: 'Get milk!'
+ };
+
+ const mocks = [
+ {
+ request: {
+ query: CREATE_TODO_MUTATION,
+ variables
+ },
+ result: {
+ data: CREATE_TODO_RESULT,
+ errors: [new GraphQLError(CREATE_TODO_ERROR)],
+ },
+ }
+ ];
+
+ let fetchResult: any;
+ const Component = () => {
+ const [createTodo] = useMutation<{ createTodo: Todo }>(
+ CREATE_TODO_MUTATION,
+ {
+ onError: error => {
+ expect(error.message).toEqual(CREATE_TODO_ERROR);
+ }
+ }
+ );
+
+ async function runMutation() {
+ fetchResult = await createTodo({ variables });
+ }
+
+ useEffect(() => {
+ runMutation();
+ }, []);
+
+ return null;
+ };
+
+ render(
+
+
+
+ );
+
+ await wait(() => {
+ expect(fetchResult.data).toEqual(undefined);
+ expect(fetchResult.errors.message).toEqual(CREATE_TODO_ERROR);
+ }).then(resolve, reject);
+ });
+
it(`should reject when errorPolicy is 'none'`, async () => {
const variables = {
description: 'Get milk!'