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!'