Skip to content

Commit 86e35a6

Browse files
Prefer the callbacks passed to the execute function returned from useMutation (apollographql#10425)
Co-authored-by: Alessia Bellisario <alessia@apollographql.com>
1 parent ce34e5d commit 86e35a6

4 files changed

Lines changed: 104 additions & 5 deletions

File tree

.changeset/metal-hats-cough.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@apollo/client': patch
3+
---
4+
5+
Prefer the `onError` and `onCompleted` callback functions passed to the execute function returned from `useMutation` instead of calling both callback handlers.

docs/shared/mutation-result.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ A function to trigger the mutation from your UI. You can optionally pass this fu
2323
* `awaitRefetchQueries`
2424
* `context`
2525
* `fetchPolicy`
26+
* `onCompleted`
27+
* `onError`
2628
* `optimisticResponse`
2729
* `refetchQueries`
2830
* `update`

src/react/hooks/__tests__/useMutation.test.tsx

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,54 @@ describe('useMutation Hook', () => {
659659
expect(onError).toHaveBeenCalledTimes(0);
660660
});
661661

662+
it('prefers the onCompleted handler passed to the execution function rather than the hook', async () => {
663+
const CREATE_TODO_DATA = {
664+
createTodo: {
665+
id: 1,
666+
priority: 'Low',
667+
description: 'Get milk!',
668+
__typename: 'Todo',
669+
},
670+
};
671+
const variables = {
672+
priority: 'Low',
673+
description: 'Get milk.',
674+
}
675+
const mocks = [
676+
{
677+
request: {
678+
query: CREATE_TODO_MUTATION,
679+
variables,
680+
},
681+
result: {
682+
data: CREATE_TODO_DATA
683+
},
684+
}
685+
];
686+
687+
const hookOnCompleted = jest.fn();
688+
689+
const { result } = renderHook(
690+
() => useMutation(CREATE_TODO_MUTATION, { onCompleted: hookOnCompleted }),
691+
{
692+
wrapper: ({ children }) => (
693+
<MockedProvider mocks={mocks}>
694+
{children}
695+
</MockedProvider>
696+
)
697+
},
698+
);
699+
700+
const [createTodo] = result.current;
701+
const onCompleted = jest.fn();
702+
await act(async () => {
703+
await createTodo({ variables, onCompleted });
704+
});
705+
706+
expect(onCompleted).toHaveBeenCalledTimes(1);
707+
expect(hookOnCompleted).not.toHaveBeenCalled();
708+
});
709+
662710
it('should allow passing an onError handler to the execution function', async () => {
663711
const errors = [new GraphQLError(CREATE_TODO_ERROR)];
664712
const variables = {
@@ -712,6 +760,46 @@ describe('useMutation Hook', () => {
712760
expect(onError).toHaveBeenCalledWith(errors[0], expect.objectContaining({variables}));
713761
});
714762

763+
it('prefers the onError handler passed to the execution function instead of the hook', async () => {
764+
const variables = {
765+
priority: 'Low',
766+
description: 'Get milk.',
767+
}
768+
const mocks = [
769+
{
770+
request: {
771+
query: CREATE_TODO_MUTATION,
772+
variables,
773+
},
774+
result: {
775+
errors: [new GraphQLError(CREATE_TODO_ERROR)],
776+
},
777+
}
778+
];
779+
780+
const hookOnError = jest.fn();
781+
782+
const { result } = renderHook(
783+
() => useMutation(CREATE_TODO_MUTATION, { onError: hookOnError }),
784+
{
785+
wrapper: ({ children }) => (
786+
<MockedProvider mocks={mocks}>
787+
{children}
788+
</MockedProvider>
789+
)
790+
},
791+
);
792+
793+
const [createTodo] = result.current;
794+
const onError = jest.fn();
795+
await act(async () => {
796+
await createTodo({ variables, onError });
797+
});
798+
799+
expect(onError).toHaveBeenCalledTimes(1);
800+
expect(hookOnError).not.toHaveBeenCalled();
801+
});
802+
715803
it('should allow updating onError while mutation is executing', async () => {
716804
const errors = [new GraphQLError(CREATE_TODO_ERROR)];
717805
const variables = {

src/react/hooks/useMutation.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,10 @@ export function useMutation<
100100
setResult(ref.current.result = result);
101101
}
102102
}
103-
ref.current.options?.onCompleted?.(response.data!, clientOptions);
104-
executeOptions.onCompleted?.(response.data!, clientOptions);
103+
104+
const onCompleted = executeOptions.onCompleted || ref.current.options?.onCompleted
105+
onCompleted?.(response.data!, clientOptions);
106+
105107
return response;
106108
}).catch((error) => {
107109
if (
@@ -121,9 +123,11 @@ export function useMutation<
121123
}
122124
}
123125

124-
if (ref.current.options?.onError || clientOptions.onError) {
125-
ref.current.options?.onError?.(error, clientOptions);
126-
executeOptions.onError?.(error, clientOptions);
126+
const onError = executeOptions.onError || ref.current.options?.onError
127+
128+
if (onError) {
129+
onError(error, clientOptions);
130+
127131
// TODO(brian): why are we returning this here???
128132
return { data: void 0, errors: error };
129133
}

0 commit comments

Comments
 (0)