From 8ecd36facc64dbb4f042207f8a53d11b8f63cac6 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Wed, 15 Nov 2023 15:12:51 +0100 Subject: [PATCH 1/9] `useSuspenseQuery`: remove `promiseCache` --- .../hooks/__tests__/useSuspenseQuery.test.tsx | 5 ++-- src/react/hooks/useSuspenseQuery.ts | 29 ++++++------------- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/src/react/hooks/__tests__/useSuspenseQuery.test.tsx b/src/react/hooks/__tests__/useSuspenseQuery.test.tsx index 468fe8f4fc5..2ebd57db009 100644 --- a/src/react/hooks/__tests__/useSuspenseQuery.test.tsx +++ b/src/react/hooks/__tests__/useSuspenseQuery.test.tsx @@ -3600,7 +3600,7 @@ describe("useSuspenseQuery", () => { jest.useRealTimers(); }); - it("tears down subscription when throwing an error on refetch", async () => { + it("xxx tears down subscription when throwing an error on refetch", async () => { using _consoleSpy = spyOnConsole("error"); const query = gql` @@ -3650,8 +3650,7 @@ describe("useSuspenseQuery", () => { }); await waitFor(() => expect(renders.errorCount).toBe(1)); - - expect(client.getObservableQueries().size).toBe(0); + await waitFor(() => expect(client.getObservableQueries().size).toBe(0)); }); it('throws network errors when errorPolicy is set to "none"', async () => { diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts index ed203d4cfee..ca8830aed2e 100644 --- a/src/react/hooks/useSuspenseQuery.ts +++ b/src/react/hooks/useSuspenseQuery.ts @@ -196,29 +196,25 @@ export function useSuspenseQuery< client.watchQuery(watchQueryOptions) ); - const [promiseCache, setPromiseCache] = React.useState( - () => new Map([[queryRef.key, queryRef.promise]]) - ); + let [current, setPromise] = React.useState< + [CacheKey, Promise>] + >([queryRef.key, queryRef.promise]); - let promise = promiseCache.get(queryRef.key); + let promise = current[0] === queryRef.key ? current[1] : undefined; if (queryRef.didChangeOptions(watchQueryOptions)) { - promise = queryRef.applyOptions(watchQueryOptions); - promiseCache.set(queryRef.key, promise); + current[1] = promise = queryRef.applyOptions(watchQueryOptions); } if (!promise) { - promise = queryRef.promise; - promiseCache.set(queryRef.key, promise); + current[1] = promise = queryRef.promise; } React.useEffect(() => { const dispose = queryRef.retain(); const removeListener = queryRef.listen((promise) => { - setPromiseCache((promiseCache) => - new Map(promiseCache).set(queryRef.key, promise) - ); + setPromise([queryRef.key, promise]); }); return () => { @@ -239,14 +235,10 @@ export function useSuspenseQuery< }, [queryRef.result]); const result = fetchPolicy === "standby" ? skipResult : __use(promise); - const fetchMore = React.useCallback( ((options) => { const promise = queryRef.fetchMore(options); - - setPromiseCache((previousPromiseCache) => - new Map(previousPromiseCache).set(queryRef.key, queryRef.promise) - ); + setPromise([queryRef.key, queryRef.promise]); return promise; }) satisfies FetchMoreFunction< @@ -259,10 +251,7 @@ export function useSuspenseQuery< const refetch: RefetchFunction = React.useCallback( (variables) => { const promise = queryRef.refetch(variables); - - setPromiseCache((previousPromiseCache) => - new Map(previousPromiseCache).set(queryRef.key, queryRef.promise) - ); + setPromise([queryRef.key, queryRef.promise]); return promise; }, From b8c51afa545d4cd04257acafea0ec9672c1afee9 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Wed, 15 Nov 2023 15:15:47 +0100 Subject: [PATCH 2/9] missed cleanup --- src/react/hooks/__tests__/useSuspenseQuery.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/react/hooks/__tests__/useSuspenseQuery.test.tsx b/src/react/hooks/__tests__/useSuspenseQuery.test.tsx index 2ebd57db009..7e53013855d 100644 --- a/src/react/hooks/__tests__/useSuspenseQuery.test.tsx +++ b/src/react/hooks/__tests__/useSuspenseQuery.test.tsx @@ -3600,7 +3600,7 @@ describe("useSuspenseQuery", () => { jest.useRealTimers(); }); - it("xxx tears down subscription when throwing an error on refetch", async () => { + it("tears down subscription when throwing an error on refetch", async () => { using _consoleSpy = spyOnConsole("error"); const query = gql` From 532505ac1f1f75ef4b52928487917f26e7123289 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Thu, 16 Nov 2023 13:58:24 +0100 Subject: [PATCH 3/9] update `current[0]` correctly --- src/react/hooks/useSuspenseQuery.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts index ca8830aed2e..daabb7e3eeb 100644 --- a/src/react/hooks/useSuspenseQuery.ts +++ b/src/react/hooks/useSuspenseQuery.ts @@ -200,16 +200,16 @@ export function useSuspenseQuery< [CacheKey, Promise>] >([queryRef.key, queryRef.promise]); - let promise = current[0] === queryRef.key ? current[1] : undefined; + if (current[0] !== queryRef.key) { + current[0] = queryRef.key; + current[1] = queryRef.promise; + } + let promise = current[1]; if (queryRef.didChangeOptions(watchQueryOptions)) { current[1] = promise = queryRef.applyOptions(watchQueryOptions); } - if (!promise) { - current[1] = promise = queryRef.promise; - } - React.useEffect(() => { const dispose = queryRef.retain(); From 50c2149f8691f9f48e3c0214ef671a075a0f87b9 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Thu, 16 Nov 2023 13:58:49 +0100 Subject: [PATCH 4/9] change `key` to empty object we only care about identity, not about contents --- src/react/cache/QueryReference.ts | 7 +++---- src/react/cache/types.ts | 4 ++++ src/react/hooks/useSuspenseQuery.ts | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/react/cache/QueryReference.ts b/src/react/cache/QueryReference.ts index 5b2fc14c643..b4069fa9f36 100644 --- a/src/react/cache/QueryReference.ts +++ b/src/react/cache/QueryReference.ts @@ -12,7 +12,7 @@ import { createFulfilledPromise, createRejectedPromise, } from "../../utilities/index.js"; -import type { CacheKey } from "./types.js"; +import type { CacheKey, QueryKey } from "./types.js"; import type { useBackgroundQuery, useReadQuery } from "../hooks/index.js"; type Listener = (promise: Promise>) => void; @@ -65,10 +65,10 @@ type ObservedOptions = Pick< export class InternalQueryReference { public result: ApolloQueryResult; - public readonly key: CacheKey; + public readonly key: QueryKey = {}; public readonly observable: ObservableQuery; - public promiseCache?: Map>>; + public promiseCache?: Map>>; public promise: Promise>; private subscription: ObservableSubscription; @@ -92,7 +92,6 @@ export class InternalQueryReference { // Don't save this result as last result to prevent delivery of last result // when first subscribing this.result = observable.getCurrentResult(false); - this.key = options.key; if (options.onDispose) { this.onDispose = options.onDispose; diff --git a/src/react/cache/types.ts b/src/react/cache/types.ts index e9e0ba8b826..40f3c4cc8fc 100644 --- a/src/react/cache/types.ts +++ b/src/react/cache/types.ts @@ -5,3 +5,7 @@ export type CacheKey = [ stringifiedVariables: string, ...queryKey: any[], ]; + +export interface QueryKey { + __queryKey?: string; +} diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts index daabb7e3eeb..fd1df0129f2 100644 --- a/src/react/hooks/useSuspenseQuery.ts +++ b/src/react/hooks/useSuspenseQuery.ts @@ -25,7 +25,7 @@ import { getSuspenseCache } from "../cache/index.js"; import { canonicalStringify } from "../../cache/index.js"; import { skipToken } from "./constants.js"; import type { SkipToken } from "./constants.js"; -import type { CacheKey } from "../cache/types.js"; +import type { CacheKey, QueryKey } from "../cache/types.js"; export interface UseSuspenseQueryResult< TData = unknown, @@ -197,7 +197,7 @@ export function useSuspenseQuery< ); let [current, setPromise] = React.useState< - [CacheKey, Promise>] + [QueryKey, Promise>] >([queryRef.key, queryRef.promise]); if (current[0] !== queryRef.key) { From 152baac76239a6c19dc85cb33276492549404a91 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Thu, 30 Nov 2023 18:42:18 +0100 Subject: [PATCH 5/9] Update src/react/hooks/useSuspenseQuery.ts --- src/react/hooks/useSuspenseQuery.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts index fd1df0129f2..8de82bf471e 100644 --- a/src/react/hooks/useSuspenseQuery.ts +++ b/src/react/hooks/useSuspenseQuery.ts @@ -200,6 +200,7 @@ export function useSuspenseQuery< [QueryKey, Promise>] >([queryRef.key, queryRef.promise]); + // This saves us a re-execution of the render function when a variable changed. if (current[0] !== queryRef.key) { current[0] = queryRef.key; current[1] = queryRef.promise; From 3ff58fb7973ee7d71cb1f342938c11ee974f4790 Mon Sep 17 00:00:00 2001 From: jerelmiller Date: Fri, 1 Dec 2023 00:35:18 +0000 Subject: [PATCH 6/9] Clean up Prettier, Size-limit, and Api-Extractor --- .api-reports/api-report-react.md | 14 +++++++++++--- .api-reports/api-report-react_hooks.md | 14 +++++++++++--- .api-reports/api-report.md | 14 +++++++++++--- .size-limits.json | 2 +- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/.api-reports/api-report-react.md b/.api-reports/api-report-react.md index 65a4bbe7d1f..df1ca8f8840 100644 --- a/.api-reports/api-report-react.md +++ b/.api-reports/api-report-react.md @@ -915,10 +915,10 @@ class InternalQueryReference { // // (undocumented) fetchMore(options: FetchMoreOptions): Promise>; - // Warning: (ae-forgotten-export) The symbol "CacheKey" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "QueryKey" needs to be exported by the entry point index.d.ts // // (undocumented) - readonly key: CacheKey; + readonly key: QueryKey; // Warning: (ae-forgotten-export) The symbol "Listener" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -928,7 +928,7 @@ class InternalQueryReference { // (undocumented) promise: Promise>; // (undocumented) - promiseCache?: Map>>; + promiseCache?: Map>>; // (undocumented) refetch(variables: OperationVariables | undefined): Promise>; // (undocumented) @@ -943,6 +943,8 @@ class InternalQueryReference { interface InternalQueryReferenceOptions { // (undocumented) autoDisposeTimeoutMs?: number; + // Warning: (ae-forgotten-export) The symbol "CacheKey" needs to be exported by the entry point index.d.ts + // // (undocumented) key: CacheKey; // (undocumented) @@ -1536,6 +1538,12 @@ class QueryInfo { variables?: Record; } +// @public (undocumented) +interface QueryKey { + // (undocumented) + __queryKey?: string; +} + // @public (undocumented) export interface QueryLazyOptions { // (undocumented) diff --git a/.api-reports/api-report-react_hooks.md b/.api-reports/api-report-react_hooks.md index 2aed4efbb46..f12894a41e8 100644 --- a/.api-reports/api-report-react_hooks.md +++ b/.api-reports/api-report-react_hooks.md @@ -862,10 +862,10 @@ class InternalQueryReference { // // (undocumented) fetchMore(options: FetchMoreOptions): Promise>; - // Warning: (ae-forgotten-export) The symbol "CacheKey" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "QueryKey" needs to be exported by the entry point index.d.ts // // (undocumented) - readonly key: CacheKey; + readonly key: QueryKey; // Warning: (ae-forgotten-export) The symbol "Listener" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -875,7 +875,7 @@ class InternalQueryReference { // (undocumented) promise: Promise>; // (undocumented) - promiseCache?: Map>>; + promiseCache?: Map>>; // (undocumented) refetch(variables: OperationVariables | undefined): Promise>; // (undocumented) @@ -890,6 +890,8 @@ class InternalQueryReference { interface InternalQueryReferenceOptions { // (undocumented) autoDisposeTimeoutMs?: number; + // Warning: (ae-forgotten-export) The symbol "CacheKey" needs to be exported by the entry point index.d.ts + // // (undocumented) key: CacheKey; // (undocumented) @@ -1463,6 +1465,12 @@ class QueryInfo { variables?: Record; } +// @public (undocumented) +interface QueryKey { + // (undocumented) + __queryKey?: string; +} + // @public (undocumented) type QueryListener = (queryInfo: QueryInfo) => void; diff --git a/.api-reports/api-report.md b/.api-reports/api-report.md index 9e336a0c50a..0f0e02b5869 100644 --- a/.api-reports/api-report.md +++ b/.api-reports/api-report.md @@ -1275,10 +1275,10 @@ class InternalQueryReference { // // (undocumented) fetchMore(options: FetchMoreOptions_2): Promise>; - // Warning: (ae-forgotten-export) The symbol "CacheKey" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "QueryKey" needs to be exported by the entry point index.d.ts // // (undocumented) - readonly key: CacheKey; + readonly key: QueryKey; // Warning: (ae-forgotten-export) The symbol "Listener" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -1288,7 +1288,7 @@ class InternalQueryReference { // (undocumented) promise: Promise>; // (undocumented) - promiseCache?: Map>>; + promiseCache?: Map>>; // (undocumented) refetch(variables: OperationVariables | undefined): Promise>; // (undocumented) @@ -1303,6 +1303,8 @@ class InternalQueryReference { interface InternalQueryReferenceOptions { // (undocumented) autoDisposeTimeoutMs?: number; + // Warning: (ae-forgotten-export) The symbol "CacheKey" needs to be exported by the entry point index.d.ts + // // (undocumented) key: CacheKey; // (undocumented) @@ -2093,6 +2095,12 @@ class QueryInfo { variables?: Record; } +// @public (undocumented) +interface QueryKey { + // (undocumented) + __queryKey?: string; +} + // @public (undocumented) export interface QueryLazyOptions { // (undocumented) diff --git a/.size-limits.json b/.size-limits.json index e196670f752..e2e2ffadd61 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 38632, + "dist/apollo-client.min.cjs": 38619, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32215 } From 4665fd421386f8aad7feb7ff73f2deb69547591f Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 1 Dec 2023 11:07:42 +0100 Subject: [PATCH 7/9] review feedback --- src/react/cache/QueryReference.ts | 3 +-- src/react/cache/SuspenseCache.ts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/react/cache/QueryReference.ts b/src/react/cache/QueryReference.ts index b4069fa9f36..7da866b5309 100644 --- a/src/react/cache/QueryReference.ts +++ b/src/react/cache/QueryReference.ts @@ -12,7 +12,7 @@ import { createFulfilledPromise, createRejectedPromise, } from "../../utilities/index.js"; -import type { CacheKey, QueryKey } from "./types.js"; +import type { QueryKey } from "./types.js"; import type { useBackgroundQuery, useReadQuery } from "../hooks/index.js"; type Listener = (promise: Promise>) => void; @@ -32,7 +32,6 @@ export interface QueryReference { } interface InternalQueryReferenceOptions { - key: CacheKey; onDispose?: () => void; autoDisposeTimeoutMs?: number; } diff --git a/src/react/cache/SuspenseCache.ts b/src/react/cache/SuspenseCache.ts index af883c0a52a..36641c76cb4 100644 --- a/src/react/cache/SuspenseCache.ts +++ b/src/react/cache/SuspenseCache.ts @@ -38,7 +38,6 @@ export class SuspenseCache { if (!ref.current) { ref.current = new InternalQueryReference(createObservable(), { - key: cacheKey, autoDisposeTimeoutMs: this.options.autoDisposeTimeoutMs, onDispose: () => { delete ref.current; From 92d91d12e19e5d76ac3047c87dd67633fc2eff90 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 1 Dec 2023 11:17:35 +0100 Subject: [PATCH 8/9] size-limit --- .size-limits.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limits.json b/.size-limits.json index e2e2ffadd61..2bb37086103 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 38619, + "dist/apollo-client.min.cjs": 38614, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32215 } From 3f5b734d3c89b320bae5b25bc196b215f68263e8 Mon Sep 17 00:00:00 2001 From: phryneas Date: Fri, 1 Dec 2023 10:20:10 +0000 Subject: [PATCH 9/9] Clean up Prettier, Size-limit, and Api-Extractor --- .api-reports/api-report-react.md | 11 ----------- .api-reports/api-report-react_hooks.md | 11 ----------- .api-reports/api-report.md | 11 ----------- .size-limits.json | 4 ++-- 4 files changed, 2 insertions(+), 35 deletions(-) diff --git a/.api-reports/api-report-react.md b/.api-reports/api-report-react.md index 21c3be9b4d2..4b531996957 100644 --- a/.api-reports/api-report-react.md +++ b/.api-reports/api-report-react.md @@ -512,13 +512,6 @@ namespace Cache_2 { import Fragment = DataProxy.Fragment; } -// @public (undocumented) -type CacheKey = [ -query: DocumentNode, -stringifiedVariables: string, -...queryKey: any[] -]; - // @public (undocumented) const enum CacheWriteBehavior { // (undocumented) @@ -944,10 +937,6 @@ class InternalQueryReference { interface InternalQueryReferenceOptions { // (undocumented) autoDisposeTimeoutMs?: number; - // Warning: (ae-forgotten-export) The symbol "CacheKey" needs to be exported by the entry point index.d.ts - // - // (undocumented) - key: CacheKey; // (undocumented) onDispose?: () => void; } diff --git a/.api-reports/api-report-react_hooks.md b/.api-reports/api-report-react_hooks.md index f6e05fbf56e..cbeb6aa48a7 100644 --- a/.api-reports/api-report-react_hooks.md +++ b/.api-reports/api-report-react_hooks.md @@ -488,13 +488,6 @@ namespace Cache_2 { import Fragment = DataProxy.Fragment; } -// @public (undocumented) -type CacheKey = [ -query: DocumentNode, -stringifiedVariables: string, -...queryKey: any[] -]; - // @public (undocumented) const enum CacheWriteBehavior { // (undocumented) @@ -891,10 +884,6 @@ class InternalQueryReference { interface InternalQueryReferenceOptions { // (undocumented) autoDisposeTimeoutMs?: number; - // Warning: (ae-forgotten-export) The symbol "CacheKey" needs to be exported by the entry point index.d.ts - // - // (undocumented) - key: CacheKey; // (undocumented) onDispose?: () => void; } diff --git a/.api-reports/api-report.md b/.api-reports/api-report.md index b7db454272d..1b63551c899 100644 --- a/.api-reports/api-report.md +++ b/.api-reports/api-report.md @@ -502,13 +502,6 @@ class CacheGroup { resetCaching(): void; } -// @public (undocumented) -type CacheKey = [ -query: DocumentNode, -stringifiedVariables: string, -...queryKey: any[] -]; - // @public (undocumented) const enum CacheWriteBehavior { // (undocumented) @@ -1303,10 +1296,6 @@ class InternalQueryReference { interface InternalQueryReferenceOptions { // (undocumented) autoDisposeTimeoutMs?: number; - // Warning: (ae-forgotten-export) The symbol "CacheKey" needs to be exported by the entry point index.d.ts - // - // (undocumented) - key: CacheKey; // (undocumented) onDispose?: () => void; } diff --git a/.size-limits.json b/.size-limits.json index 2bb37086103..dbf215c6ea5 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 38614, - "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32215 + "dist/apollo-client.min.cjs": 38675, + "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32306 }