From ee407ef97317bf29c554732237aaf11552e06b01 Mon Sep 17 00:00:00 2001 From: Alessia Bellisario Date: Wed, 28 Jun 2023 07:39:16 -0400 Subject: [PATCH] Support `returnPartialData` and `refetchWritePolicy` in `useBackgroundQuery` (#10960) --- .changeset/spotty-news-stare.md | 5 + .../__tests__/useBackgroundQuery.test.tsx | 1582 +++++++++++++++-- src/react/hooks/useBackgroundQuery.ts | 63 +- src/react/types/types.ts | 2 + src/utilities/index.ts | 4 +- 5 files changed, 1483 insertions(+), 173 deletions(-) create mode 100644 .changeset/spotty-news-stare.md diff --git a/.changeset/spotty-news-stare.md b/.changeset/spotty-news-stare.md new file mode 100644 index 00000000000..7dd57152d5c --- /dev/null +++ b/.changeset/spotty-news-stare.md @@ -0,0 +1,5 @@ +--- +'@apollo/client': patch +--- + +Adds support for `returnPartialData` and `refetchWritePolicy` options in `useBackgroundQuery` hook. diff --git a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx index cd98fad9409..dedce0e08da 100644 --- a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx +++ b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx @@ -34,7 +34,11 @@ import { MockSubscriptionLink, mockSingleLink, } from '../../../testing'; -import { concatPagination, offsetLimitPagination } from '../../../utilities'; +import { + concatPagination, + offsetLimitPagination, + DeepPartial, +} from '../../../utilities'; import { useBackgroundQuery } from '../useBackgroundQuery'; import { useReadQuery } from '../useReadQuery'; import { ApolloProvider } from '../../context'; @@ -46,6 +50,7 @@ import { RefetchFunction, QueryReference, } from '../../../react'; +import { SuspenseQueryHookOptions } from '../../types/types'; import equal from '@wry/equality'; function renderIntegrationTest({ @@ -105,20 +110,18 @@ function renderIntegrationTest({ function Child({ queryRef }: { queryRef: QueryReference }) { const { data } = useReadQuery(queryRef); + // count renders in the child component + renders.count++; return
{data.foo.bar}
; } function Parent() { const [queryRef] = useBackgroundQuery(query); - // count renders in the parent component - renders.count++; return ; } function ParentWithVariables() { const [queryRef] = useBackgroundQuery(query); - // count renders in the parent component - renders.count++; return ; } @@ -173,6 +176,8 @@ function renderVariablesIntegrationTest({ variables, mocks, errorPolicy, + options, + cache, }: { mocks?: { request: { query: DocumentNode; variables: { id: string } }; @@ -186,6 +191,8 @@ function renderVariablesIntegrationTest({ }; }[]; variables: { id: string }; + options?: SuspenseQueryHookOptions; + cache?: InMemoryCache; errorPolicy?: ErrorPolicy; }) { let { mocks: _mocks, query } = useVariablesIntegrationTestCase(); @@ -213,7 +220,7 @@ function renderVariablesIntegrationTest({ ); const suspenseCache = new SuspenseCache(); const client = new ApolloClient({ - cache: new InMemoryCache(), + cache: cache || new InMemoryCache(), link: new MockLink(mocks || _mocks), }); interface Renders { @@ -261,7 +268,8 @@ function renderVariablesIntegrationTest({ }) { const { data, error, networkStatus } = useReadQuery(queryRef); const [variables, setVariables] = React.useState(_variables); - + // count renders in the child component + renders.count++; renders.frames.push({ data, networkStatus, error }); return ( @@ -294,11 +302,10 @@ function renderVariablesIntegrationTest({ errorPolicy?: ErrorPolicy; }) { const [queryRef, { refetch }] = useBackgroundQuery(query, { + ...options, variables, errorPolicy, }); - // count renders in the parent component - renders.count++; return ( ); @@ -331,7 +338,7 @@ function renderVariablesIntegrationTest({ const rerender = ({ variables }: { variables: VariablesCaseVariables }) => { return rest.rerender(); }; - return { ...rest, query, rerender, client, renders }; + return { ...rest, query, rerender, client, renders, mocks: mocks || _mocks }; } function renderPaginatedIntegrationTest({ @@ -440,7 +447,8 @@ function renderPaginatedIntegrationTest({ queryRef: QueryReference; }) { const { data, error } = useReadQuery(queryRef); - + // count renders in the child component + renders.count++; return (
{error ?
{error.message}
: null} @@ -482,8 +490,6 @@ function renderPaginatedIntegrationTest({ const [queryRef, { fetchMore }] = useBackgroundQuery(query, { variables: { limit: 2, offset: 0 }, }); - // count renders in the parent component - renders.count++; return ; } @@ -1119,7 +1125,7 @@ describe('useBackgroundQuery', () => { // the parent component re-renders when promise fulfilled expect(await screen.findByText('hello')).toBeInTheDocument(); - expect(renders.count).toBe(2); + expect(renders.count).toBe(1); }); it('works with startTransition to change variables', async () => { @@ -1411,7 +1417,7 @@ describe('useBackgroundQuery', () => { // the parent component re-renders when promise fulfilled expect(await screen.findByText('hello')).toBeInTheDocument(); - expect(renders.count).toBe(2); + expect(renders.count).toBe(1); client.writeQuery({ query, @@ -1421,6 +1427,7 @@ describe('useBackgroundQuery', () => { // the parent component re-renders when promise fulfilled expect(await screen.findByText('baz')).toBeInTheDocument(); + expect(renders.count).toBe(2); expect(renders.suspenseCount).toBe(1); client.writeQuery({ @@ -1984,7 +1991,7 @@ describe('useBackgroundQuery', () => { // parent component re-suspends expect(renders.suspenseCount).toBe(2); - expect(renders.count).toBe(4); + expect(renders.count).toBe(2); expect( await screen.findByText('1 - Spider-Man (updated)') @@ -2049,7 +2056,7 @@ describe('useBackgroundQuery', () => { // parent component re-suspends expect(renders.suspenseCount).toBe(2); - expect(renders.count).toBe(4); + expect(renders.count).toBe(3); // extra render puts an additional frame into the array expect(renders.frames).toMatchObject([ @@ -2086,7 +2093,7 @@ describe('useBackgroundQuery', () => { // parent component re-suspends expect(renders.suspenseCount).toBe(2); - expect(renders.count).toBe(4); + expect(renders.count).toBe(2); expect( await screen.findByText('1 - Spider-Man (updated)') @@ -2096,7 +2103,7 @@ describe('useBackgroundQuery', () => { // parent component re-suspends expect(renders.suspenseCount).toBe(3); - expect(renders.count).toBe(6); + expect(renders.count).toBe(3); expect( await screen.findByText('1 - Spider-Man (updated again)') @@ -2496,7 +2503,7 @@ describe('useBackgroundQuery', () => { // parent component re-suspends expect(renders.suspenseCount).toBe(2); await waitFor(() => { - expect(renders.count).toBe(4); + expect(renders.count).toBe(2); }); expect(getItemTexts()).toStrictEqual(['C', 'D']); @@ -2521,7 +2528,7 @@ describe('useBackgroundQuery', () => { // parent component re-suspends expect(renders.suspenseCount).toBe(2); await waitFor(() => { - expect(renders.count).toBe(4); + expect(renders.count).toBe(2); }); const moreItems = await screen.findAllByTestId(/letter/i); @@ -2547,7 +2554,7 @@ describe('useBackgroundQuery', () => { // parent component re-suspends expect(renders.suspenseCount).toBe(2); await waitFor(() => { - expect(renders.count).toBe(4); + expect(renders.count).toBe(2); }); const moreItems = await screen.findAllByTestId(/letter/i); @@ -2723,183 +2730,1462 @@ describe('useBackgroundQuery', () => { expect(todo1).toHaveTextContent('Clean room'); }); }); - }); - - describe.skip('type tests', () => { - it('disallows returnPartialData in BackgroundQueryHookOptions', () => { - const { query } = renderIntegrationTest(); - - // @ts-expect-error should not allow returnPartialData in options - useBackgroundQuery(query, { returnPartialData: true }); - }); - it('disallows refetchWritePolicy in BackgroundQueryHookOptions', () => { - const { query } = renderIntegrationTest(); - - // @ts-expect-error should not allow refetchWritePolicy in options - useBackgroundQuery(query, { refetchWritePolicy: 'overwrite' }); - }); + it('honors refetchWritePolicy set to "merge"', async () => { + const user = userEvent.setup(); - it('returns unknown when TData cannot be inferred', () => { - const query = gql` - query { - hello + const query: TypedDocumentNode< + { primes: number[] }, + { min: number; max: number } + > = gql` + query GetPrimes($min: number, $max: number) { + primes(min: $min, max: $max) } `; - const [queryRef] = useBackgroundQuery(query); - const { data } = useReadQuery(queryRef); + interface QueryData { + primes: number[]; + } - expectTypeOf(data).toEqualTypeOf(); - }); + const mocks = [ + { + request: { query, variables: { min: 0, max: 12 } }, + result: { data: { primes: [2, 3, 5, 7, 11] } }, + }, + { + request: { query, variables: { min: 12, max: 30 } }, + result: { data: { primes: [13, 17, 19, 23, 29] } }, + delay: 10, + }, + ]; - it('disallows wider variables type than specified', () => { - const { query } = useVariablesIntegrationTestCase(); + const mergeParams: [number[] | undefined, number[]][] = []; + const cache = new InMemoryCache({ + typePolicies: { + Query: { + fields: { + primes: { + keyArgs: false, + merge(existing: number[] | undefined, incoming: number[]) { + mergeParams.push([existing, incoming]); + return existing ? existing.concat(incoming) : incoming; + }, + }, + }, + }, + }, + }); - // @ts-expect-error should not allow wider TVariables type - useBackgroundQuery(query, { variables: { id: '1', foo: 'bar' } }); - }); + function SuspenseFallback() { + return
loading
; + } - it('returns TData in default case', () => { - const { query } = useVariablesIntegrationTestCase(); + const client = new ApolloClient({ + link: new MockLink(mocks), + cache, + }); - const [inferredQueryRef] = useBackgroundQuery(query); - const { data: inferred } = useReadQuery(inferredQueryRef); + const suspenseCache = new SuspenseCache(); - expectTypeOf(inferred).toEqualTypeOf(); - expectTypeOf(inferred).not.toEqualTypeOf(); + function Child({ + refetch, + queryRef, + }: { + refetch: ( + variables?: Partial | undefined + ) => Promise>; + queryRef: QueryReference; + }) { + const { data, error, networkStatus } = useReadQuery(queryRef); - const [explicitQueryRef] = useBackgroundQuery< - VariablesCaseData, - VariablesCaseVariables - >(query); + return ( +
+ +
{data?.primes.join(', ')}
+
{networkStatus}
+
{error?.message || 'undefined'}
+
+ ); + } - const { data: explicit } = useReadQuery(explicitQueryRef); + function Parent() { + const [queryRef, { refetch }] = useBackgroundQuery(query, { + variables: { min: 0, max: 12 }, + refetchWritePolicy: 'merge', + }); + return ; + } - expectTypeOf(explicit).toEqualTypeOf(); - expectTypeOf(explicit).not.toEqualTypeOf(); - }); + function App() { + return ( + + }> + + + + ); + } - it('returns TData | undefined with errorPolicy: "ignore"', () => { - const { query } = useVariablesIntegrationTestCase(); + render(); - const [inferredQueryRef] = useBackgroundQuery(query, { - errorPolicy: 'ignore', + await waitFor(() => { + expect(screen.getByTestId('primes')).toHaveTextContent( + '2, 3, 5, 7, 11' + ); }); - const { data: inferred } = useReadQuery(inferredQueryRef); + expect(screen.getByTestId('network-status')).toHaveTextContent( + '7' // ready + ); + expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(mergeParams).toEqual([[undefined, [2, 3, 5, 7, 11]]]); - expectTypeOf(inferred).toEqualTypeOf(); - expectTypeOf(inferred).not.toEqualTypeOf(); + await act(() => user.click(screen.getByText('Refetch'))); - const [explicitQueryRef] = useBackgroundQuery< - VariablesCaseData, - VariablesCaseVariables - >(query, { - errorPolicy: 'ignore', + await waitFor(() => { + expect(screen.getByTestId('primes')).toHaveTextContent( + '2, 3, 5, 7, 11, 13, 17, 19, 23, 29' + ); }); + expect(screen.getByTestId('network-status')).toHaveTextContent( + '7' // ready + ); + expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(mergeParams).toEqual([ + [undefined, [2, 3, 5, 7, 11]], + [ + [2, 3, 5, 7, 11], + [13, 17, 19, 23, 29], + ], + ]); + }); - const { data: explicit } = useReadQuery(explicitQueryRef); + it('defaults refetchWritePolicy to "overwrite"', async () => { + const user = userEvent.setup(); - expectTypeOf(explicit).toEqualTypeOf(); - expectTypeOf(explicit).not.toEqualTypeOf(); - }); + const query: TypedDocumentNode< + { primes: number[] }, + { min: number; max: number } + > = gql` + query GetPrimes($min: number, $max: number) { + primes(min: $min, max: $max) + } + `; - it('returns TData | undefined with errorPolicy: "all"', () => { - const { query } = useVariablesIntegrationTestCase(); + interface QueryData { + primes: number[]; + } - const [inferredQueryRef] = useBackgroundQuery(query, { - errorPolicy: 'all', + const mocks = [ + { + request: { query, variables: { min: 0, max: 12 } }, + result: { data: { primes: [2, 3, 5, 7, 11] } }, + }, + { + request: { query, variables: { min: 12, max: 30 } }, + result: { data: { primes: [13, 17, 19, 23, 29] } }, + delay: 10, + }, + ]; + + const mergeParams: [number[] | undefined, number[]][] = []; + const cache = new InMemoryCache({ + typePolicies: { + Query: { + fields: { + primes: { + keyArgs: false, + merge(existing: number[] | undefined, incoming: number[]) { + mergeParams.push([existing, incoming]); + return existing ? existing.concat(incoming) : incoming; + }, + }, + }, + }, + }, }); - const { data: inferred } = useReadQuery(inferredQueryRef); - expectTypeOf(inferred).toEqualTypeOf(); - expectTypeOf(inferred).not.toEqualTypeOf(); + function SuspenseFallback() { + return
loading
; + } - const [explicitQueryRef] = useBackgroundQuery(query, { - errorPolicy: 'all', + const client = new ApolloClient({ + link: new MockLink(mocks), + cache, }); - const { data: explicit } = useReadQuery(explicitQueryRef); - - expectTypeOf(explicit).toEqualTypeOf(); - expectTypeOf(explicit).not.toEqualTypeOf(); - }); - it('returns TData with errorPolicy: "none"', () => { - const { query } = useVariablesIntegrationTestCase(); + const suspenseCache = new SuspenseCache(); - const [inferredQueryRef] = useBackgroundQuery(query, { - errorPolicy: 'none', - }); - const { data: inferred } = useReadQuery(inferredQueryRef); + function Child({ + refetch, + queryRef, + }: { + refetch: ( + variables?: Partial | undefined + ) => Promise>; + queryRef: QueryReference; + }) { + const { data, error, networkStatus } = useReadQuery(queryRef); - expectTypeOf(inferred).toEqualTypeOf(); - expectTypeOf(inferred).not.toEqualTypeOf(); + return ( +
+ +
{data?.primes.join(', ')}
+
{networkStatus}
+
{error?.message || 'undefined'}
+
+ ); + } - const [explicitQueryRef] = useBackgroundQuery(query, { - errorPolicy: 'none', - }); - const { data: explicit } = useReadQuery(explicitQueryRef); + function Parent() { + const [queryRef, { refetch }] = useBackgroundQuery(query, { + variables: { min: 0, max: 12 }, + }); + return ; + } - expectTypeOf(explicit).toEqualTypeOf(); - expectTypeOf(explicit).not.toEqualTypeOf(); - }); + function App() { + return ( + + }> + + + + ); + } - it('returns TData | undefined when `skip` is present', () => { - const { query } = useVariablesIntegrationTestCase(); + render(); - const [inferredQueryRef] = useBackgroundQuery(query, { - skip: true, + await waitFor(() => { + expect(screen.getByTestId('primes')).toHaveTextContent( + '2, 3, 5, 7, 11' + ); }); + expect(screen.getByTestId('network-status')).toHaveTextContent( + '7' // ready + ); + expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(mergeParams).toEqual([[undefined, [2, 3, 5, 7, 11]]]); - const { data: inferred } = useReadQuery(inferredQueryRef); - - expectTypeOf(inferred).toEqualTypeOf(); - expectTypeOf(inferred).not.toEqualTypeOf(); + await act(() => user.click(screen.getByText('Refetch'))); - const [explicitQueryRef] = useBackgroundQuery(query, { - skip: true, + await waitFor(() => { + expect(screen.getByTestId('primes')).toHaveTextContent( + '13, 17, 19, 23, 29' + ); }); + expect(screen.getByTestId('network-status')).toHaveTextContent( + '7' // ready + ); + expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + expect(mergeParams).toEqual([ + [undefined, [2, 3, 5, 7, 11]], + [undefined, [13, 17, 19, 23, 29]], + ]); + }); - const { data: explicit } = useReadQuery(explicitQueryRef); + it('does not suspend when partial data is in the cache and using a "cache-first" fetch policy with returnPartialData', async () => { + interface Data { + character: { + id: string; + name: string; + }; + } - expectTypeOf(explicit).toEqualTypeOf(); - expectTypeOf(explicit).not.toEqualTypeOf(); + const fullQuery: TypedDocumentNode = gql` + query { + character { + id + name + } + } + `; - // TypeScript is too smart and using a `const` or `let` boolean variable - // for the `skip` option results in a false positive. Using an options - // object allows us to properly check for a dynamic case. - const options = { - skip: true, + const partialQuery = gql` + query { + character { + id + } + } + `; + const mocks = [ + { + request: { query: fullQuery }, + result: { data: { character: { id: '1', name: 'Doctor Strange' } } }, + }, + ]; + + interface Renders { + errors: Error[]; + errorCount: number; + suspenseCount: number; + count: number; + } + const renders: Renders = { + errors: [], + errorCount: 0, + suspenseCount: 0, + count: 0, }; - const [dynamicQueryRef] = useBackgroundQuery(query, { - skip: options.skip, - }); + const cache = new InMemoryCache(); - const { data: dynamic } = useReadQuery(dynamicQueryRef); + cache.writeQuery({ + query: partialQuery, + data: { character: { id: '1' } }, + }); - expectTypeOf(dynamic).toEqualTypeOf(); - expectTypeOf(dynamic).not.toEqualTypeOf(); + const client = new ApolloClient({ + link: new MockLink(mocks), + cache, + }); + + const suspenseCache = new SuspenseCache(); + + function App() { + return ( + + }> + + + + ); + } + + function SuspenseFallback() { + renders.suspenseCount++; + return

Loading

; + } + + function Parent() { + const [queryRef] = useBackgroundQuery(fullQuery, { + fetchPolicy: 'cache-first', + returnPartialData: true, + }); + return ; + } + + function Todo({ + queryRef, + }: { + queryRef: QueryReference>; + }) { + const { data, networkStatus, error } = useReadQuery(queryRef); + renders.count++; + + return ( + <> +
{data.character?.id}
+
{data.character?.name}
+
{networkStatus}
+
{error?.message || 'undefined'}
+ + ); + } + + render(); + + expect(renders.suspenseCount).toBe(0); + expect(screen.getByTestId('character-id')).toHaveTextContent('1'); + expect(screen.getByTestId('character-name')).toHaveTextContent(''); + expect(screen.getByTestId('network-status')).toHaveTextContent('1'); // loading + expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + + await waitFor(() => { + expect(screen.getByTestId('character-name')).toHaveTextContent( + 'Doctor Strange' + ); + }); + expect(screen.getByTestId('character-id')).toHaveTextContent('1'); + expect(screen.getByTestId('network-status')).toHaveTextContent('7'); // ready + expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + + expect(renders.count).toBe(2); + expect(renders.suspenseCount).toBe(0); + }); + + it('suspends and does not use partial data when changing variables and using a "cache-first" fetch policy with returnPartialData', async () => { + const partialQuery = gql` + query ($id: ID!) { + character(id: $id) { + id + } + } + `; + + const cache = new InMemoryCache(); + + cache.writeQuery({ + query: partialQuery, + data: { character: { id: '1' } }, + variables: { id: '1' }, + }); + + const { renders, mocks, rerender } = renderVariablesIntegrationTest({ + variables: { id: '1' }, + cache, + options: { + fetchPolicy: 'cache-first', + returnPartialData: true, + }, + }); + expect(renders.suspenseCount).toBe(0); + + expect(await screen.findByText('1 - Spider-Man')).toBeInTheDocument(); + + rerender({ variables: { id: '2' } }); + + expect(await screen.findByText('2 - Black Widow')).toBeInTheDocument(); + + expect(renders.frames[2]).toMatchObject({ + ...mocks[1].result, + networkStatus: NetworkStatus.ready, + error: undefined, + }); + + expect(renders.count).toBe(3); + expect(renders.suspenseCount).toBe(1); + expect(renders.frames).toMatchObject([ + { + data: { character: { id: '1' } }, + networkStatus: NetworkStatus.loading, + error: undefined, + }, + { + ...mocks[0].result, + networkStatus: NetworkStatus.ready, + error: undefined, + }, + { + ...mocks[1].result, + networkStatus: NetworkStatus.ready, + error: undefined, + }, + ]); + }); + + it('suspends when partial data is in the cache and using a "network-only" fetch policy with returnPartialData', async () => { + interface Data { + character: { + id: string; + name: string; + }; + } + + const fullQuery: TypedDocumentNode = gql` + query { + character { + id + name + } + } + `; + + const partialQuery = gql` + query { + character { + id + } + } + `; + const mocks = [ + { + request: { query: fullQuery }, + result: { data: { character: { id: '1', name: 'Doctor Strange' } } }, + }, + ]; + + interface Renders { + errors: Error[]; + errorCount: number; + suspenseCount: number; + count: number; + frames: { + data: DeepPartial; + networkStatus: NetworkStatus; + error: ApolloError | undefined; + }[]; + } + const renders: Renders = { + errors: [], + errorCount: 0, + suspenseCount: 0, + count: 0, + frames: [], + }; + + const cache = new InMemoryCache(); + + cache.writeQuery({ + query: partialQuery, + data: { character: { id: '1' } }, + }); + + const client = new ApolloClient({ + link: new MockLink(mocks), + cache, + }); + + const suspenseCache = new SuspenseCache(); + + function App() { + return ( + + }> + + + + ); + } + + function SuspenseFallback() { + renders.suspenseCount++; + return

Loading

; + } + + function Parent() { + const [queryRef] = useBackgroundQuery(fullQuery, { + fetchPolicy: 'network-only', + returnPartialData: true, + }); + + return ; + } + + function Todo({ + queryRef, + }: { + queryRef: QueryReference>; + }) { + const { data, networkStatus, error } = useReadQuery(queryRef); + renders.frames.push({ data, networkStatus, error }); + renders.count++; + return ( + <> +
{data.character?.id}
+
{data.character?.name}
+
{networkStatus}
+
{error?.message || 'undefined'}
+ + ); + } + + render(); + + expect(renders.suspenseCount).toBe(1); + + await waitFor(() => { + expect(screen.getByTestId('character-name')).toHaveTextContent( + 'Doctor Strange' + ); + }); + expect(screen.getByTestId('character-id')).toHaveTextContent('1'); + expect(screen.getByTestId('network-status')).toHaveTextContent('7'); // ready + expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + + expect(renders.count).toBe(1); + expect(renders.suspenseCount).toBe(1); + + expect(renders.frames).toMatchObject([ + { + ...mocks[0].result, + networkStatus: NetworkStatus.ready, + error: undefined, + }, + ]); + }); + + it('suspends when partial data is in the cache and using a "no-cache" fetch policy with returnPartialData', async () => { + const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); + interface Data { + character: { + id: string; + name: string; + }; + } + + const fullQuery: TypedDocumentNode = gql` + query { + character { + id + name + } + } + `; + + const partialQuery = gql` + query { + character { + id + } + } + `; + const mocks = [ + { + request: { query: fullQuery }, + result: { data: { character: { id: '1', name: 'Doctor Strange' } } }, + }, + ]; + + interface Renders { + errors: Error[]; + errorCount: number; + suspenseCount: number; + count: number; + frames: { + data: DeepPartial; + networkStatus: NetworkStatus; + error: ApolloError | undefined; + }[]; + } + const renders: Renders = { + errors: [], + errorCount: 0, + suspenseCount: 0, + count: 0, + frames: [], + }; + + const cache = new InMemoryCache(); + + cache.writeQuery({ + query: partialQuery, + data: { character: { id: '1' } }, + }); + + const client = new ApolloClient({ + link: new MockLink(mocks), + cache, + }); + + const suspenseCache = new SuspenseCache(); + + function App() { + return ( + + }> + + + + ); + } + + function SuspenseFallback() { + renders.suspenseCount++; + return

Loading

; + } + + function Parent() { + const [queryRef] = useBackgroundQuery(fullQuery, { + fetchPolicy: 'no-cache', + returnPartialData: true, + }); + + return ; + } + + function Todo({ + queryRef, + }: { + queryRef: QueryReference>; + }) { + const { data, networkStatus, error } = useReadQuery(queryRef); + renders.frames.push({ data, networkStatus, error }); + renders.count++; + return ( + <> +
{data.character?.id}
+
{data.character?.name}
+
{networkStatus}
+
{error?.message || 'undefined'}
+ + ); + } + + render(); + + expect(renders.suspenseCount).toBe(1); + + await waitFor(() => { + expect(screen.getByTestId('character-name')).toHaveTextContent( + 'Doctor Strange' + ); + }); + expect(screen.getByTestId('character-id')).toHaveTextContent('1'); + expect(screen.getByTestId('network-status')).toHaveTextContent('7'); // ready + expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + + expect(renders.count).toBe(1); + expect(renders.suspenseCount).toBe(1); + + expect(renders.frames).toMatchObject([ + { + ...mocks[0].result, + networkStatus: NetworkStatus.ready, + error: undefined, + }, + ]); + + consoleSpy.mockRestore(); + }); + + it('warns when using returnPartialData with a "no-cache" fetch policy', async () => { + const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); + + const query: TypedDocumentNode = gql` + query UserQuery { + greeting + } + `; + const mocks = [ + { + request: { query }, + result: { data: { greeting: 'Hello' } }, + }, + ]; + + renderSuspenseHook( + () => + useBackgroundQuery(query, { + fetchPolicy: 'no-cache', + returnPartialData: true, + }), + { mocks } + ); + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith( + 'Using `returnPartialData` with a `no-cache` fetch policy has no effect. To read partial data from the cache, consider using an alternate fetch policy.' + ); + + consoleSpy.mockRestore(); + }); + + it('does not suspend when partial data is in the cache and using a "cache-and-network" fetch policy with returnPartialData', async () => { + interface Data { + character: { + id: string; + name: string; + }; + } + + const fullQuery: TypedDocumentNode = gql` + query { + character { + id + name + } + } + `; + + const partialQuery = gql` + query { + character { + id + } + } + `; + const mocks = [ + { + request: { query: fullQuery }, + result: { data: { character: { id: '1', name: 'Doctor Strange' } } }, + }, + ]; + + interface Renders { + errors: Error[]; + errorCount: number; + suspenseCount: number; + count: number; + frames: { + data: DeepPartial; + networkStatus: NetworkStatus; + error: ApolloError | undefined; + }[]; + } + const renders: Renders = { + errors: [], + errorCount: 0, + suspenseCount: 0, + count: 0, + frames: [], + }; + + const cache = new InMemoryCache(); + + cache.writeQuery({ + query: partialQuery, + data: { character: { id: '1' } }, + }); + + const client = new ApolloClient({ + link: new MockLink(mocks), + cache, + }); + + const suspenseCache = new SuspenseCache(); + + function App() { + return ( + + }> + + + + ); + } + + function SuspenseFallback() { + renders.suspenseCount++; + return

Loading

; + } + + function Parent() { + const [queryRef] = useBackgroundQuery(fullQuery, { + fetchPolicy: 'cache-and-network', + returnPartialData: true, + }); + + return ; + } + + function Todo({ + queryRef, + }: { + queryRef: QueryReference>; + }) { + const { data, networkStatus, error } = useReadQuery(queryRef); + renders.frames.push({ data, networkStatus, error }); + renders.count++; + return ( + <> +
{data.character?.id}
+
{data.character?.name}
+
{networkStatus}
+
{error?.message || 'undefined'}
+ + ); + } + + render(); + + expect(renders.suspenseCount).toBe(0); + expect(screen.getByTestId('character-id')).toHaveTextContent('1'); + // name is not present yet, since it's missing in partial data + expect(screen.getByTestId('character-name')).toHaveTextContent(''); + expect(screen.getByTestId('network-status')).toHaveTextContent('1'); // loading + expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + + await waitFor(() => { + expect(screen.getByTestId('character-name')).toHaveTextContent( + 'Doctor Strange' + ); + }); + expect(screen.getByTestId('character-id')).toHaveTextContent('1'); + expect(screen.getByTestId('network-status')).toHaveTextContent('7'); // ready + expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + + expect(renders.count).toBe(2); + expect(renders.suspenseCount).toBe(0); + + expect(renders.frames).toMatchObject([ + { + data: { character: { id: '1' } }, + networkStatus: NetworkStatus.loading, + error: undefined, + }, + { + ...mocks[0].result, + networkStatus: NetworkStatus.ready, + error: undefined, + }, + ]); + }); + + it('suspends and does not use partial data when changing variables and using a "cache-and-network" fetch policy with returnPartialData', async () => { + const partialQuery = gql` + query ($id: ID!) { + character(id: $id) { + id + } + } + `; + + const cache = new InMemoryCache(); + + cache.writeQuery({ + query: partialQuery, + data: { character: { id: '1' } }, + variables: { id: '1' }, + }); + + const { renders, mocks, rerender } = renderVariablesIntegrationTest({ + variables: { id: '1' }, + cache, + options: { + fetchPolicy: 'cache-and-network', + returnPartialData: true, + }, + }); + + expect(renders.suspenseCount).toBe(0); + + expect(await screen.findByText('1 - Spider-Man')).toBeInTheDocument(); + + rerender({ variables: { id: '2' } }); + + expect(await screen.findByText('2 - Black Widow')).toBeInTheDocument(); + + expect(renders.count).toBe(3); + expect(renders.suspenseCount).toBe(1); + expect(renders.frames).toMatchObject([ + { + data: { character: { id: '1' } }, + networkStatus: NetworkStatus.loading, + error: undefined, + }, + { + ...mocks[0].result, + networkStatus: NetworkStatus.ready, + error: undefined, + }, + { + ...mocks[1].result, + networkStatus: NetworkStatus.ready, + error: undefined, + }, + ]); + }); + + it('does not suspend deferred queries with partial data in the cache and using a "cache-first" fetch policy with `returnPartialData`', async () => { + interface QueryData { + greeting: { + __typename: string; + message?: string; + recipient?: { + __typename: string; + name: string; + }; + }; + } + + const query: TypedDocumentNode = gql` + query { + greeting { + message + ... on Greeting @defer { + recipient { + name + } + } + } + } + `; + + const link = new MockSubscriptionLink(); + const cache = new InMemoryCache(); + + // We are intentionally writing partial data to the cache. Supress console + // warnings to avoid unnecessary noise in the test. + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); + cache.writeQuery({ + query, + data: { + greeting: { + __typename: 'Greeting', + recipient: { __typename: 'Person', name: 'Cached Alice' }, + }, + }, + }); + consoleSpy.mockRestore(); + + interface Renders { + errors: Error[]; + errorCount: number; + suspenseCount: number; + count: number; + frames: { + data: DeepPartial; + networkStatus: NetworkStatus; + error: ApolloError | undefined; + }[]; + } + const renders: Renders = { + errors: [], + errorCount: 0, + suspenseCount: 0, + count: 0, + frames: [], + }; + + const client = new ApolloClient({ + link, + cache, + }); + + const suspenseCache = new SuspenseCache(); + + function App() { + return ( + + }> + + + + ); + } + + function SuspenseFallback() { + renders.suspenseCount++; + return

Loading

; + } + + function Parent() { + const [queryRef] = useBackgroundQuery(query, { + fetchPolicy: 'cache-first', + returnPartialData: true, + }); + + return ; + } + + function Todo({ + queryRef, + }: { + queryRef: QueryReference>; + }) { + const { data, networkStatus, error } = useReadQuery(queryRef); + renders.frames.push({ data, networkStatus, error }); + renders.count++; + return ( + <> +
{data.greeting?.message}
+
{data.greeting?.recipient?.name}
+
{networkStatus}
+
{error?.message || 'undefined'}
+ + ); + } + + render(); + + expect(renders.suspenseCount).toBe(0); + expect(screen.getByTestId('recipient')).toHaveTextContent('Cached Alice'); + // message is not present yet, since it's missing in partial data + expect(screen.getByTestId('message')).toHaveTextContent(''); + expect(screen.getByTestId('network-status')).toHaveTextContent('1'); // loading + expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + + link.simulateResult({ + result: { + data: { + greeting: { message: 'Hello world', __typename: 'Greeting' }, + }, + hasNext: true, + }, + }); + + await waitFor(() => { + expect(screen.getByTestId('message')).toHaveTextContent('Hello world'); + }); + expect(screen.getByTestId('recipient')).toHaveTextContent('Cached Alice'); + expect(screen.getByTestId('network-status')).toHaveTextContent('7'); // ready + expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + + link.simulateResult({ + result: { + incremental: [ + { + data: { + __typename: 'Greeting', + recipient: { name: 'Alice', __typename: 'Person' }, + }, + path: ['greeting'], + }, + ], + hasNext: false, + }, + }); + + await waitFor(() => { + expect(screen.getByTestId('recipient').textContent).toEqual('Alice'); + }); + expect(screen.getByTestId('message')).toHaveTextContent('Hello world'); + expect(screen.getByTestId('network-status')).toHaveTextContent('7'); // ready + expect(screen.getByTestId('error')).toHaveTextContent('undefined'); + + expect(renders.count).toBe(3); + expect(renders.suspenseCount).toBe(0); + expect(renders.frames).toMatchObject([ + { + data: { + greeting: { + __typename: 'Greeting', + recipient: { __typename: 'Person', name: 'Cached Alice' }, + }, + }, + networkStatus: NetworkStatus.loading, + error: undefined, + }, + { + data: { + greeting: { + __typename: 'Greeting', + message: 'Hello world', + recipient: { __typename: 'Person', name: 'Cached Alice' }, + }, + }, + networkStatus: NetworkStatus.ready, + error: undefined, + }, + { + data: { + greeting: { + __typename: 'Greeting', + message: 'Hello world', + recipient: { __typename: 'Person', name: 'Alice' }, + }, + }, + networkStatus: NetworkStatus.ready, + error: undefined, + }, + ]); + }); + }); + + describe.skip('type tests', () => { + it('returns unknown when TData cannot be inferred', () => { + const query = gql` + query { + hello + } + `; + + const [queryRef] = useBackgroundQuery(query); + const { data } = useReadQuery(queryRef); + + expectTypeOf(data).toEqualTypeOf(); + }); + + it('disallows wider variables type than specified', () => { + const { query } = useVariablesIntegrationTestCase(); + + // @ts-expect-error should not allow wider TVariables type + useBackgroundQuery(query, { variables: { id: '1', foo: 'bar' } }); + }); + + it('returns TData in default case', () => { + const { query } = useVariablesIntegrationTestCase(); + + const [inferredQueryRef] = useBackgroundQuery(query); + const { data: inferred } = useReadQuery(inferredQueryRef); + + expectTypeOf(inferred).toEqualTypeOf(); + expectTypeOf(inferred).not.toEqualTypeOf(); + + const [explicitQueryRef] = useBackgroundQuery< + VariablesCaseData, + VariablesCaseVariables + >(query); + + const { data: explicit } = useReadQuery(explicitQueryRef); + + expectTypeOf(explicit).toEqualTypeOf(); + expectTypeOf(explicit).not.toEqualTypeOf(); + }); + + it('returns TData | undefined with errorPolicy: "ignore"', () => { + const { query } = useVariablesIntegrationTestCase(); + + const [inferredQueryRef] = useBackgroundQuery(query, { + errorPolicy: 'ignore', + }); + const { data: inferred } = useReadQuery(inferredQueryRef); + + expectTypeOf(inferred).toEqualTypeOf(); + expectTypeOf(inferred).not.toEqualTypeOf(); + + const [explicitQueryRef] = useBackgroundQuery< + VariablesCaseData, + VariablesCaseVariables + >(query, { + errorPolicy: 'ignore', + }); + + const { data: explicit } = useReadQuery(explicitQueryRef); + + expectTypeOf(explicit).toEqualTypeOf(); + expectTypeOf(explicit).not.toEqualTypeOf(); + }); + + it('returns TData | undefined with errorPolicy: "all"', () => { + const { query } = useVariablesIntegrationTestCase(); + + const [inferredQueryRef] = useBackgroundQuery(query, { + errorPolicy: 'all', + }); + const { data: inferred } = useReadQuery(inferredQueryRef); + + expectTypeOf(inferred).toEqualTypeOf(); + expectTypeOf(inferred).not.toEqualTypeOf(); + + const [explicitQueryRef] = useBackgroundQuery(query, { + errorPolicy: 'all', + }); + const { data: explicit } = useReadQuery(explicitQueryRef); + + expectTypeOf(explicit).toEqualTypeOf(); + expectTypeOf(explicit).not.toEqualTypeOf(); + }); + + it('returns TData with errorPolicy: "none"', () => { + const { query } = useVariablesIntegrationTestCase(); + + const [inferredQueryRef] = useBackgroundQuery(query, { + errorPolicy: 'none', + }); + const { data: inferred } = useReadQuery(inferredQueryRef); + + expectTypeOf(inferred).toEqualTypeOf(); + expectTypeOf(inferred).not.toEqualTypeOf(); + + const [explicitQueryRef] = useBackgroundQuery(query, { + errorPolicy: 'none', + }); + const { data: explicit } = useReadQuery(explicitQueryRef); + + expectTypeOf(explicit).toEqualTypeOf(); + expectTypeOf(explicit).not.toEqualTypeOf(); + }); + + it('returns DeepPartial with returnPartialData: true', () => { + const { query } = useVariablesIntegrationTestCase(); + + const [inferredQueryRef] = useBackgroundQuery(query, { + returnPartialData: true, + }); + const { data: inferred } = useReadQuery(inferredQueryRef); + + expectTypeOf(inferred).toEqualTypeOf>(); + expectTypeOf(inferred).not.toEqualTypeOf(); + + const [explicitQueryRef] = useBackgroundQuery< + VariablesCaseData, + VariablesCaseVariables + >(query, { + returnPartialData: true, + }); + + const { data: explicit } = useReadQuery(explicitQueryRef); + + expectTypeOf(explicit).toEqualTypeOf>(); + expectTypeOf(explicit).not.toEqualTypeOf(); + }); + + it('returns TData with returnPartialData: false', () => { + const { query } = useVariablesIntegrationTestCase(); + + const [inferredQueryRef] = useBackgroundQuery(query, { + returnPartialData: false, + }); + const { data: inferred } = useReadQuery(inferredQueryRef); + + expectTypeOf(inferred).toEqualTypeOf(); + expectTypeOf(inferred).not.toEqualTypeOf< + DeepPartial + >(); + + const [explicitQueryRef] = useBackgroundQuery< + VariablesCaseData, + VariablesCaseVariables + >(query, { + returnPartialData: false, + }); + + const { data: explicit } = useReadQuery(explicitQueryRef); + + expectTypeOf(explicit).toEqualTypeOf(); + expectTypeOf(explicit).not.toEqualTypeOf< + DeepPartial + >(); + }); + + it('returns TData when passing an option that does not affect TData', () => { + const { query } = useVariablesIntegrationTestCase(); + + const [inferredQueryRef] = useBackgroundQuery(query, { + fetchPolicy: 'no-cache', + }); + const { data: inferred } = useReadQuery(inferredQueryRef); + + expectTypeOf(inferred).toEqualTypeOf(); + expectTypeOf(inferred).not.toEqualTypeOf< + DeepPartial + >(); + + const [explicitQueryRef] = useBackgroundQuery< + VariablesCaseData, + VariablesCaseVariables + >(query, { + fetchPolicy: 'no-cache', + }); + + const { data: explicit } = useReadQuery(explicitQueryRef); + + expectTypeOf(explicit).toEqualTypeOf(); + expectTypeOf(explicit).not.toEqualTypeOf< + DeepPartial + >(); + }); + + it('handles combinations of options', () => { + const { query } = useVariablesIntegrationTestCase(); + + const [inferredPartialDataIgnoreQueryRef] = useBackgroundQuery(query, { + returnPartialData: true, + errorPolicy: 'ignore', + }); + const { data: inferredPartialDataIgnore } = useReadQuery( + inferredPartialDataIgnoreQueryRef + ); + + expectTypeOf(inferredPartialDataIgnore).toEqualTypeOf< + DeepPartial | undefined + >(); + expectTypeOf( + inferredPartialDataIgnore + ).not.toEqualTypeOf(); + + const [explicitPartialDataIgnoreQueryRef] = useBackgroundQuery< + VariablesCaseData, + VariablesCaseVariables + >(query, { + returnPartialData: true, + errorPolicy: 'ignore', + }); + + const { data: explicitPartialDataIgnore } = useReadQuery( + explicitPartialDataIgnoreQueryRef + ); + + expectTypeOf(explicitPartialDataIgnore).toEqualTypeOf< + DeepPartial | undefined + >(); + expectTypeOf( + explicitPartialDataIgnore + ).not.toEqualTypeOf(); + + const [inferredPartialDataNoneQueryRef] = useBackgroundQuery(query, { + returnPartialData: true, + errorPolicy: 'none', + }); + + const { data: inferredPartialDataNone } = useReadQuery( + inferredPartialDataNoneQueryRef + ); + + expectTypeOf(inferredPartialDataNone).toEqualTypeOf< + DeepPartial + >(); + expectTypeOf( + inferredPartialDataNone + ).not.toEqualTypeOf(); + + const [explicitPartialDataNoneQueryRef] = useBackgroundQuery< + VariablesCaseData, + VariablesCaseVariables + >(query, { + returnPartialData: true, + errorPolicy: 'none', + }); + + const { data: explicitPartialDataNone } = useReadQuery( + explicitPartialDataNoneQueryRef + ); + + expectTypeOf(explicitPartialDataNone).toEqualTypeOf< + DeepPartial + >(); + expectTypeOf( + explicitPartialDataNone + ).not.toEqualTypeOf(); + }); + + it('returns correct TData type when combined options that do not affect TData', () => { + const { query } = useVariablesIntegrationTestCase(); + + const [inferredQueryRef] = useBackgroundQuery(query, { + fetchPolicy: 'no-cache', + returnPartialData: true, + errorPolicy: 'none', + }); + const { data: inferred } = useReadQuery(inferredQueryRef); + + expectTypeOf(inferred).toEqualTypeOf>(); + expectTypeOf(inferred).not.toEqualTypeOf(); + + const [explicitQueryRef] = useBackgroundQuery< + VariablesCaseData, + VariablesCaseVariables + >(query, { + fetchPolicy: 'no-cache', + returnPartialData: true, + errorPolicy: 'none', + }); + + const { data: explicit } = useReadQuery(explicitQueryRef); + + expectTypeOf(explicit).toEqualTypeOf>(); + expectTypeOf(explicit).not.toEqualTypeOf(); }); - // TODO: https://github.com/apollographql/apollo-client/issues/10893 - // it('returns DeepPartial with returnPartialData: true', () => { - // }); + it('returns TData | undefined when `skip` is present', () => { + const { query } = useVariablesIntegrationTestCase(); + + const [inferredQueryRef] = useBackgroundQuery(query, { + skip: true, + }); + + const { data: inferred } = useReadQuery(inferredQueryRef); + + expectTypeOf(inferred).toEqualTypeOf(); + expectTypeOf(inferred).not.toEqualTypeOf(); + + const [explicitQueryRef] = useBackgroundQuery(query, { + skip: true, + }); + + const { data: explicit } = useReadQuery(explicitQueryRef); + + expectTypeOf(explicit).toEqualTypeOf(); + expectTypeOf(explicit).not.toEqualTypeOf(); - // TODO: https://github.com/apollographql/apollo-client/issues/10893 - // it('returns TData with returnPartialData: false', () => { - // }); + // TypeScript is too smart and using a `const` or `let` boolean variable + // for the `skip` option results in a false positive. Using an options + // object allows us to properly check for a dynamic case. + const options = { + skip: true, + }; - // TODO: https://github.com/apollographql/apollo-client/issues/10893 - // it('returns TData when passing an option that does not affect TData', () => { - // }); + const [dynamicQueryRef] = useBackgroundQuery(query, { + skip: options.skip, + }); - // TODO: https://github.com/apollographql/apollo-client/issues/10893 - // it('handles combinations of options', () => { - // }); + const { data: dynamic } = useReadQuery(dynamicQueryRef); - // TODO: https://github.com/apollographql/apollo-client/issues/10893 - // it('returns correct TData type when combined options that do not affect TData', () => { - // }); + expectTypeOf(dynamic).toEqualTypeOf(); + expectTypeOf(dynamic).not.toEqualTypeOf(); + }); }); }); diff --git a/src/react/hooks/useBackgroundQuery.ts b/src/react/hooks/useBackgroundQuery.ts index 1b5afd4f537..ce4641dcc97 100644 --- a/src/react/hooks/useBackgroundQuery.ts +++ b/src/react/hooks/useBackgroundQuery.ts @@ -28,17 +28,27 @@ export type UseBackgroundQueryResult< } ]; +type BackgroundQueryHookOptionsNoInfer< + TData, + TVariables extends OperationVariables +> = BackgroundQueryHookOptions, NoInfer>; + export function useBackgroundQuery< TData, TVariables extends OperationVariables, TOptions extends Omit, 'variables'> >( query: DocumentNode | TypedDocumentNode, - options?: BackgroundQueryHookOptions, NoInfer> & - TOptions + options?: BackgroundQueryHookOptionsNoInfer & TOptions ): UseBackgroundQueryResult< TOptions['errorPolicy'] extends 'ignore' | 'all' - ? TData | undefined + ? TOptions['returnPartialData'] extends true + ? DeepPartial | undefined + : TData | undefined + : TOptions['returnPartialData'] extends true + ? TOptions['skip'] extends boolean + ? DeepPartial | undefined + : DeepPartial : TOptions['skip'] extends boolean ? TData | undefined : TData, @@ -50,7 +60,7 @@ export function useBackgroundQuery< TVariables extends OperationVariables = OperationVariables >( query: DocumentNode | TypedDocumentNode, - options: BackgroundQueryHookOptions, NoInfer> & { + options: BackgroundQueryHookOptionsNoInfer & { returnPartialData: true; errorPolicy: 'ignore' | 'all'; } @@ -61,7 +71,7 @@ export function useBackgroundQuery< TVariables extends OperationVariables = OperationVariables >( query: DocumentNode | TypedDocumentNode, - options: BackgroundQueryHookOptions, NoInfer> & { + options: BackgroundQueryHookOptionsNoInfer & { errorPolicy: 'ignore' | 'all'; } ): UseBackgroundQueryResult; @@ -71,30 +81,38 @@ export function useBackgroundQuery< TVariables extends OperationVariables = OperationVariables >( query: DocumentNode | TypedDocumentNode, - options: BackgroundQueryHookOptions, NoInfer> & { + options: BackgroundQueryHookOptionsNoInfer & { skip: boolean; + returnPartialData: true; } -): UseBackgroundQueryResult; +): UseBackgroundQueryResult | undefined, TVariables>; -// TODO: support `returnPartialData` | `refetchWritePolicy` -// see https://github.com/apollographql/apollo-client/issues/10893 +export function useBackgroundQuery< + TData = unknown, + TVariables extends OperationVariables = OperationVariables +>( + query: DocumentNode | TypedDocumentNode, + options: BackgroundQueryHookOptionsNoInfer & { + returnPartialData: true; + } +): UseBackgroundQueryResult, TVariables>; -// export function useBackgroundQuery< -// TData = unknown, -// TVariables extends OperationVariables = OperationVariables -// >( -// query: DocumentNode | TypedDocumentNode, -// options: SuspenseQueryHookOptions, NoInfer> & { -// returnPartialData: true; -// } -// ): UseBackgroundQueryResult, TVariables>; +export function useBackgroundQuery< + TData = unknown, + TVariables extends OperationVariables = OperationVariables +>( + query: DocumentNode | TypedDocumentNode, + options: BackgroundQueryHookOptionsNoInfer & { + skip: boolean; + } +): UseBackgroundQueryResult; export function useBackgroundQuery< TData = unknown, TVariables extends OperationVariables = OperationVariables >( query: DocumentNode | TypedDocumentNode, - options?: BackgroundQueryHookOptions, NoInfer> + options?: BackgroundQueryHookOptionsNoInfer ): UseBackgroundQueryResult; export function useBackgroundQuery< @@ -102,10 +120,9 @@ export function useBackgroundQuery< TVariables extends OperationVariables = OperationVariables >( query: DocumentNode | TypedDocumentNode, - options: BackgroundQueryHookOptions< - NoInfer, - NoInfer - > = Object.create(null) + options: BackgroundQueryHookOptionsNoInfer = Object.create( + null + ) ): UseBackgroundQueryResult { const suspenseCache = useSuspenseCache(options.suspenseCache); const client = useApolloClient(options.client); diff --git a/src/react/types/types.ts b/src/react/types/types.ts index c988a842857..a63deda1d26 100644 --- a/src/react/types/types.ts +++ b/src/react/types/types.ts @@ -149,6 +149,8 @@ export interface BackgroundQueryHookOptions< | 'errorPolicy' | 'context' | 'canonizeResults' + | 'returnPartialData' + | 'refetchWritePolicy' | 'skip' > { fetchPolicy?: BackgroundQueryHookFetchPolicy; diff --git a/src/utilities/index.ts b/src/utilities/index.ts index 24773f897ee..b0dc4a17873 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -12,7 +12,7 @@ export { getInclusionDirectives, } from './graphql/directives'; -export { +export { DocumentTransform, DocumentTransformCacheKey } from './graphql/DocumentTransform'; @@ -96,7 +96,7 @@ export { ObservableSubscription } from './observables/Observable'; -export { +export { isStatefulPromise, createFulfilledPromise, createRejectedPromise,