diff --git a/Changelog.md b/Changelog.md index b90efed345..14a467ae86 100644 --- a/Changelog.md +++ b/Changelog.md @@ -16,6 +16,8 @@ [@joshalling](https://github.com/joshalling) in [#3417](https://github.com/apollographql/react-apollo/pull/3417) - Prevent inline `onError` and `onCompleted` callbacks from being part of the internal memoization that's used to decide when certain after render units of functionality are run, when using `useQuery`. This fixes issues related to un-necessary component cleanup, like `error` disappearing from results when it should be present.
[@dylanwulf](https://github.com/dylanwulf) in [#3419](https://github.com/apollographql/react-apollo/pull/3419) +- `useLazyQuery`'s execution function can now be called multiple times in a row, and will properly submit network requests each time called, when using a fetch policy of `network-only`.
+ [@hwillson](https://github.com/hwillson) in [#3453](https://github.com/apollographql/react-apollo/pull/3453) - Documentation fixes.
[@SeanRoberts](https://github.com/SeanRoberts) in [#3380](https://github.com/apollographql/react-apollo/pull/3380) diff --git a/packages/hooks/src/__tests__/useLazyQuery.test.tsx b/packages/hooks/src/__tests__/useLazyQuery.test.tsx index b8209da69f..4b7062cef8 100644 --- a/packages/hooks/src/__tests__/useLazyQuery.test.tsx +++ b/packages/hooks/src/__tests__/useLazyQuery.test.tsx @@ -1,8 +1,8 @@ -import React, { useState } from 'react'; +import React from 'react'; import { DocumentNode } from 'graphql'; import gql from 'graphql-tag'; import { MockedProvider } from '@apollo/react-testing'; -import { render, cleanup } from '@testing-library/react'; +import { render, wait } from '@testing-library/react'; import { ApolloProvider, useLazyQuery } from '@apollo/react-hooks'; import { ApolloClient } from 'apollo-client'; import { InMemoryCache } from 'apollo-cache-inmemory'; @@ -38,16 +38,16 @@ describe('useLazyQuery Hook', () => { } ]; - afterEach(cleanup); - - it('should hold query execution until manually triggered', done => { + it('should hold query execution until manually triggered', async () => { let renderCount = 0; const Component = () => { const [execute, { loading, data }] = useLazyQuery(CAR_QUERY); switch (renderCount) { case 0: expect(loading).toEqual(false); - execute(); + setTimeout(() => { + execute(); + }); break; case 1: expect(loading).toEqual(true); @@ -55,7 +55,6 @@ describe('useLazyQuery Hook', () => { case 2: expect(loading).toEqual(false); expect(data).toEqual(CAR_RESULT_DATA); - done(); break; default: // Do nothing } @@ -68,11 +67,15 @@ describe('useLazyQuery Hook', () => { ); + + await wait(() => { + expect(renderCount).toBe(3); + }); }); it('should set `called` to false by default', () => { const Component = () => { - const [execute, { loading, called }] = useLazyQuery(CAR_QUERY); + const [, { loading, called }] = useLazyQuery(CAR_QUERY); expect(loading).toBeFalsy(); expect(called).toBeFalsy(); return null; @@ -85,7 +88,7 @@ describe('useLazyQuery Hook', () => { ); }); - it('should set `called` to true after calling the lazy execute function', done => { + it('should set `called` to true after calling the lazy execute function', async () => { let renderCount = 0; const Component = () => { const [execute, { loading, called, data }] = useLazyQuery(CAR_QUERY); @@ -93,7 +96,9 @@ describe('useLazyQuery Hook', () => { case 0: expect(loading).toBeFalsy(); expect(called).toBeFalsy(); - execute(); + setTimeout(() => { + execute(); + }); break; case 1: expect(loading).toBeTruthy(); @@ -103,7 +108,6 @@ describe('useLazyQuery Hook', () => { expect(loading).toEqual(false); expect(called).toBeTruthy(); expect(data).toEqual(CAR_RESULT_DATA); - done(); break; default: // Do nothing } @@ -116,9 +120,13 @@ describe('useLazyQuery Hook', () => { ); + + await wait(() => { + expect(renderCount).toBe(3); + }); }); - it('should override `skip` if lazy mode execution function is called', done => { + it('should override `skip` if lazy mode execution function is called', async () => { let renderCount = 0; const Component = () => { const [execute, { loading, data }] = useLazyQuery(CAR_QUERY, { @@ -127,7 +135,9 @@ describe('useLazyQuery Hook', () => { switch (renderCount) { case 0: expect(loading).toBeFalsy(); - execute(); + setTimeout(() => { + execute(); + }); break; case 1: expect(loading).toBeTruthy(); @@ -135,7 +145,6 @@ describe('useLazyQuery Hook', () => { case 2: expect(loading).toEqual(false); expect(data).toEqual(CAR_RESULT_DATA); - done(); break; default: // Do nothing } @@ -148,12 +157,16 @@ describe('useLazyQuery Hook', () => { ); + + await wait(() => { + expect(renderCount).toBe(3); + }); }); it( 'should use variables defined in hook options (if any), when running ' + 'the lazy execution function', - done => { + async () => { const CAR_QUERY: DocumentNode = gql` query AllCars($year: Int!) { cars(year: $year) @client { @@ -195,7 +208,9 @@ describe('useLazyQuery Hook', () => { switch (renderCount) { case 0: expect(loading).toBeFalsy(); - execute(); + setTimeout(() => { + execute(); + }); break; case 1: expect(loading).toBeTruthy(); @@ -203,7 +218,6 @@ describe('useLazyQuery Hook', () => { case 2: expect(loading).toEqual(false); expect(data.cars).toEqual([CAR_RESULT_DATA[1]]); - done(); break; default: // Do nothing } @@ -216,13 +230,17 @@ describe('useLazyQuery Hook', () => { ); + + await wait(() => { + expect(renderCount).toBe(3); + }); } ); it( 'should use variables passed into lazy execution function, ' + 'overriding similar variables defined in Hook options', - done => { + async () => { const CAR_QUERY: DocumentNode = gql` query AllCars($year: Int!) { cars(year: $year) @client { @@ -264,7 +282,9 @@ describe('useLazyQuery Hook', () => { switch (renderCount) { case 0: expect(loading).toBeFalsy(); - execute({ variables: { year: 2000 } }); + setTimeout(() => { + execute({ variables: { year: 2000 } }); + }); break; case 1: expect(loading).toBeTruthy(); @@ -272,7 +292,6 @@ describe('useLazyQuery Hook', () => { case 2: expect(loading).toEqual(false); expect(data.cars).toEqual([CAR_RESULT_DATA[0]]); - done(); break; default: // Do nothing } @@ -285,6 +304,89 @@ describe('useLazyQuery Hook', () => { ); + + await wait(() => { + expect(renderCount).toBe(3); + }); + } + ); + + it( + 'should fetch data each time the execution function is called, when ' + + 'using a "network-only" fetch policy', + async () => { + const data1 = CAR_RESULT_DATA; + + const data2 = { + cars: [ + { + make: 'Audi', + model: 'SQ5', + vin: 'POWERANDTRUNKSPACE', + __typename: 'Car' + } + ] + }; + + const mocks = [ + { + request: { + query: CAR_QUERY + }, + result: { data: data1 } + }, + { + request: { + query: CAR_QUERY + }, + result: { data: data2 } + } + ]; + + let renderCount = 0; + const Component = () => { + const [execute, { loading, data }] = useLazyQuery(CAR_QUERY, { + fetchPolicy: 'network-only' + }); + switch (renderCount) { + case 0: + expect(loading).toEqual(false); + setTimeout(() => { + execute(); + }); + break; + case 1: + expect(loading).toEqual(true); + break; + case 2: + expect(loading).toEqual(false); + expect(data).toEqual(data1); + setTimeout(() => { + execute(); + }); + break; + case 3: + expect(loading).toEqual(true); + break; + case 4: + expect(loading).toEqual(false); + expect(data).toEqual(data2); + break; + default: // Do nothing + } + renderCount += 1; + return null; + }; + + render( + + + + ); + + await wait(() => { + expect(renderCount).toBe(5); + }); } ); }); diff --git a/packages/hooks/src/data/QueryData.ts b/packages/hooks/src/data/QueryData.ts index bffc33f3cf..5c29955855 100644 --- a/packages/hooks/src/data/QueryData.ts +++ b/packages/hooks/src/data/QueryData.ts @@ -126,6 +126,7 @@ export class QueryData extends OperationData { this.currentObservable.query.resetQueryStoreErrors(); }); } + return this.unmount.bind(this); } @@ -159,6 +160,8 @@ export class QueryData extends OperationData { } private runLazyQuery = (options?: QueryLazyOptions) => { + this.cleanup(); + this.runLazy = true; this.lazyOptions = options; this.forceUpdate();