diff --git a/src/react/data/QueryData.ts b/src/react/data/QueryData.ts index 4d3ad689830..b1aa3c66990 100644 --- a/src/react/data/QueryData.ts +++ b/src/react/data/QueryData.ts @@ -239,12 +239,7 @@ export class QueryData extends OperationData { children: null }; - if ( - !equal( - newObservableQueryOptions, - this.previousData.observableQueryOptions - ) - ) { + if (this.haveOptionsChanged(newObservableQueryOptions)) { this.previousData.observableQueryOptions = newObservableQueryOptions; this.currentObservable .setOptions(newObservableQueryOptions) @@ -484,4 +479,23 @@ export class QueryData extends OperationData { subscribeToMore: this.obsSubscribeToMore } as ObservableQueryFields; } + + private haveOptionsChanged(options: QueryDataOptions) { + // When comparing new options against previously stored options, + // we'll ignore callback functions since their identities are not + // stable, meaning they'll always show as being different. Ignoring + // them when determining if options have changed is okay however, as + // callback functions are not normally changed between renders. + const emptyCallbacks = { onCompleted: undefined, onError: undefined }; + return !equal( + { + ...options, + ...emptyCallbacks + }, + { + ...this.previousData.observableQueryOptions, + ...emptyCallbacks + } + ); + } } diff --git a/src/react/hooks/__tests__/useQuery.test.tsx b/src/react/hooks/__tests__/useQuery.test.tsx index 9ec841088e5..1787d437494 100644 --- a/src/react/hooks/__tests__/useQuery.test.tsx +++ b/src/react/hooks/__tests__/useQuery.test.tsx @@ -1798,6 +1798,43 @@ describe('useQuery Hook', () => { expect(renderCount).toBe(3); }).then(resolve, reject); }); + + itAsync( + 'should not make extra network requests when `onCompleted` is ' + + 'defined with a `network-only` fetch policy', + (resolve, reject) => { + let renderCount = 0; + function Component() { + const { loading, data } = useQuery(CAR_QUERY, { + fetchPolicy: 'network-only', + onCompleted: () => undefined + }); + switch (++renderCount) { + case 1: + expect(loading).toBeTruthy(); + break; + case 2: + expect(loading).toBeFalsy(); + expect(data).toEqual(CAR_RESULT_DATA); + break; + case 3: + fail('Too many renders'); + default: + } + return null; + } + + render( + + + + ); + + return wait(() => { + expect(renderCount).toBe(2); + }).then(resolve, reject); + } + ); }); describe('Optimistic data', () => {