Skip to content

Commit

Permalink
Prevent undefined mutation result in useMutation (#8018)
Browse files Browse the repository at this point in the history
* Prevent undefined response when onError is provided

* Update CHANGELOG.md
  • Loading branch information
jcreighton authored Apr 23, 2021
1 parent 2ec1fe8 commit 9b766ad
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 8 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. <br/>
[@benjamn](https://github.com/benjamn) in [#7983](https://github.com/apollographql/apollo-client/pull/7983)

- Prevent `undefined` mutation result in useMutation <br/>
[@jcreighton](https://github.com/jcreighton) in [#8018](https://github.com/apollographql/apollo-client/pull/8018)

## Apollo Client 3.3.14

### Improvements
Expand Down
20 changes: 12 additions & 8 deletions src/react/data/MutationData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
});
};

Expand Down Expand Up @@ -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,
Expand All @@ -138,10 +145,6 @@ export class MutationData<
called: true
});
}

if (onError) {
onError(error);
}
}

private generateNewMutationId(): number {
Expand All @@ -152,13 +155,14 @@ export class MutationData<
return this.mostRecentMutationId === mutationId;
}

private updateResult(result: MutationResultWithoutClient<TData>) {
private updateResult(result: MutationResultWithoutClient<TData>): MutationResultWithoutClient<TData> | undefined {
if (
this.isMounted &&
(!this.previousResult || !equal(this.previousResult, result))
) {
this.setResult(result);
this.previousResult = result;
return result;
}
}
}
52 changes: 52 additions & 0 deletions src/react/hooks/__tests__/useMutation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<MockedProvider mocks={mocks}>
<Component />
</MockedProvider>
);

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

0 comments on commit 9b766ad

Please sign in to comment.