From c3d9abfb653545a345daf3ba8d4c7c67f8126831 Mon Sep 17 00:00:00 2001
From: Jerel Miller <jerelmiller@gmail.com>
Date: Tue, 30 Apr 2024 17:13:02 -0600
Subject: [PATCH 01/10] Add failing test for useBackgroundQuery issue

---
 .../__tests__/useBackgroundQuery.test.tsx     | 82 +++++++++++++++++++
 1 file changed, 82 insertions(+)

diff --git a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx
index 605c5ed42c1..36fa009ba73 100644
--- a/src/react/hooks/__tests__/useBackgroundQuery.test.tsx
+++ b/src/react/hooks/__tests__/useBackgroundQuery.test.tsx
@@ -536,6 +536,88 @@ it("does not recreate queryRef and execute a network request when rerendering us
   await expect(Profiler).not.toRerender({ timeout: 50 });
 });
 
+// https://github.com/apollographql/apollo-client/issues/11815
+it("does not recreate queryRef or execute a network request when rerendering useBackgroundQuery in strict mode", async () => {
+  const { query } = setupSimpleCase();
+  const user = userEvent.setup();
+  let fetchCount = 0;
+  const client = new ApolloClient({
+    link: new ApolloLink(() => {
+      fetchCount++;
+
+      return new Observable((observer) => {
+        setTimeout(() => {
+          observer.next({ data: { greeting: "Hello" } });
+          observer.complete();
+        }, 20);
+      });
+    }),
+    cache: new InMemoryCache(),
+  });
+
+  const Profiler = createProfiler({
+    initialSnapshot: {
+      queryRef: null as QueryReference<SimpleCaseData> | null,
+      result: null as UseReadQueryResult<SimpleCaseData> | null,
+    },
+  });
+  const { SuspenseFallback, ReadQueryHook } =
+    createDefaultTrackedComponents(Profiler);
+
+  function App() {
+    useTrackRenders();
+    const [, setCount] = React.useState(0);
+    // Use a fetchPolicy of no-cache to ensure we can more easily track if
+    // another network request was made
+    const [queryRef] = useBackgroundQuery(query, { fetchPolicy: "no-cache" });
+
+    Profiler.mergeSnapshot({ queryRef });
+
+    return (
+      <>
+        <button onClick={() => setCount((count) => count + 1)}>
+          Increment
+        </button>
+        <Suspense fallback={<SuspenseFallback />}>
+          <ReadQueryHook queryRef={queryRef} />
+        </Suspense>
+      </>
+    );
+  }
+
+  renderWithClient(<App />, {
+    client,
+    wrapper: ({ children }) => (
+      <Profiler>
+        <React.StrictMode>{children}</React.StrictMode>
+      </Profiler>
+    ),
+  });
+
+  const incrementButton = screen.getByText("Increment");
+
+  {
+    const { renderedComponents } = await Profiler.takeRender();
+
+    expect(renderedComponents).toStrictEqual([App, SuspenseFallback]);
+  }
+
+  // eslint-disable-next-line testing-library/render-result-naming-convention
+  const firstRender = await Profiler.takeRender();
+  const initialQueryRef = firstRender.snapshot.queryRef;
+
+  await act(() => user.click(incrementButton));
+
+  {
+    const { snapshot } = await Profiler.takeRender();
+
+    expect(snapshot.queryRef).toBe(initialQueryRef);
+    expect(fetchCount).toBe(1);
+  }
+
+  await expect(Profiler).not.toRerender({ timeout: 50 });
+});
+
 it("disposes of the queryRef when unmounting before it is used by useReadQuery", async () => {
   const { query, mocks } = setupSimpleCase();
   const client = new ApolloClient({

From 2f997d2f7a38588b14eefc5d85e7cce6b1b78342 Mon Sep 17 00:00:00 2001
From: Jerel Miller <jerelmiller@gmail.com>
Date: Tue, 30 Apr 2024 17:48:09 -0600
Subject: [PATCH 02/10] Fix issue where queryRef is disposed in useReadQuery
 and not added back to Suspense cache in strict mode

---
 src/react/hooks/useBackgroundQuery.ts      | 2 +-
 src/react/hooks/useReadQuery.ts            | 1 +
 src/react/hooks/useSuspenseQuery.ts        | 2 +-
 src/react/internal/cache/QueryReference.ts | 9 +++++++++
 src/react/internal/cache/SuspenseCache.ts  | 9 ++++-----
 5 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/src/react/hooks/useBackgroundQuery.ts b/src/react/hooks/useBackgroundQuery.ts
index 0b060b2476f..e07ecac873a 100644
--- a/src/react/hooks/useBackgroundQuery.ts
+++ b/src/react/hooks/useBackgroundQuery.ts
@@ -247,7 +247,7 @@ function _useBackgroundQuery<
   // useReadQuery will ensure the same queryRef is maintained.
   React.useEffect(() => {
     if (queryRef.disposed) {
-      suspenseCache.add(cacheKey, queryRef);
+      queryRef.strictModeFixAddToSuspenseCache();
     }
     // Omitting the deps is intentional. This avoids stale closures and the
     // conditional ensures we aren't running the logic on each render.
diff --git a/src/react/hooks/useReadQuery.ts b/src/react/hooks/useReadQuery.ts
index 6add98a094b..681a40660a4 100644
--- a/src/react/hooks/useReadQuery.ts
+++ b/src/react/hooks/useReadQuery.ts
@@ -89,6 +89,7 @@ function _useReadQuery<TData>(
     // handle strict mode where this useEffect will be run twice resulting in a
     // disposed queryRef before the next render.
     if (internalQueryRef.disposed) {
+      internalQueryRef.strictModeFixAddToSuspenseCache();
       internalQueryRef.reinitialize();
     }
 
diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts
index 0459aba5087..e6b252d470f 100644
--- a/src/react/hooks/useSuspenseQuery.ts
+++ b/src/react/hooks/useSuspenseQuery.ts
@@ -258,7 +258,7 @@ function _useSuspenseQuery<
       // We address this by adding the queryRef back to the suspense cache
       // so that the lookup on the next render uses the existing queryRef rather
       // than instantiating a new one.
-      suspenseCache.add(cacheKey, queryRef);
+      queryRef.strictModeFixAddToSuspenseCache();
       queryRef.reinitialize();
     }
     // We can omit the deps here to get a fresh closure each render since the
diff --git a/src/react/internal/cache/QueryReference.ts b/src/react/internal/cache/QueryReference.ts
index f3bd6395b8c..2f933526119 100644
--- a/src/react/internal/cache/QueryReference.ts
+++ b/src/react/internal/cache/QueryReference.ts
@@ -80,6 +80,7 @@ export interface QueryReference<TData = unknown, TVariables = unknown> {
 
 interface InternalQueryReferenceOptions {
   onDispose?: () => void;
+  addToSuspenseCache?: (queryRef: InternalQueryReference<any>) => void;
   autoDisposeTimeoutMs?: number;
 }
 
@@ -147,6 +148,9 @@ export class InternalQueryReference<TData = unknown> {
   public result!: ApolloQueryResult<TData>;
   public readonly key: QueryKey = {};
   public readonly observable: ObservableQuery<TData>;
+  private readonly addToSuspenseCache?: (
+    queryRef: InternalQueryReference<TData>
+  ) => void;
 
   public promise!: QueryRefPromise<TData>;
 
@@ -168,6 +172,7 @@ export class InternalQueryReference<TData = unknown> {
     this.handleError = this.handleError.bind(this);
     this.dispose = this.dispose.bind(this);
     this.observable = observable;
+    this.addToSuspenseCache = options.addToSuspenseCache;
 
     if (options.onDispose) {
       this.onDispose = options.onDispose;
@@ -203,6 +208,10 @@ export class InternalQueryReference<TData = unknown> {
     return this.observable.options;
   }
 
+  strictModeFixAddToSuspenseCache() {
+    this.addToSuspenseCache?.(this);
+  }
+
   reinitialize() {
     const { observable } = this;
 
diff --git a/src/react/internal/cache/SuspenseCache.ts b/src/react/internal/cache/SuspenseCache.ts
index 8b1eba321b5..4ef90f18485 100644
--- a/src/react/internal/cache/SuspenseCache.ts
+++ b/src/react/internal/cache/SuspenseCache.ts
@@ -39,6 +39,10 @@ export class SuspenseCache {
     if (!ref.current) {
       ref.current = new InternalQueryReference(createObservable(), {
         autoDisposeTimeoutMs: this.options.autoDisposeTimeoutMs,
+        addToSuspenseCache: (queryRef) => {
+          const ref = this.queryRefs.lookupArray(cacheKey);
+          ref.current = queryRef;
+        },
         onDispose: () => {
           delete ref.current;
         },
@@ -47,9 +51,4 @@ export class SuspenseCache {
 
     return ref.current;
   }
-
-  add(cacheKey: CacheKey, queryRef: InternalQueryReference<unknown>) {
-    const ref = this.queryRefs.lookupArray(cacheKey);
-    ref.current = queryRef;
-  }
 }

From 5b4e5c050abe30a7fa2a74323012e3b9870fe805 Mon Sep 17 00:00:00 2001
From: Jerel Miller <jerelmiller@gmail.com>
Date: Tue, 30 Apr 2024 17:50:47 -0600
Subject: [PATCH 03/10] Add changeset

---
 .changeset/lazy-parents-thank.md | 5 +++++
 1 file changed, 5 insertions(+)
 create mode 100644 .changeset/lazy-parents-thank.md

diff --git a/.changeset/lazy-parents-thank.md b/.changeset/lazy-parents-thank.md
new file mode 100644
index 00000000000..7e35d83112a
--- /dev/null
+++ b/.changeset/lazy-parents-thank.md
@@ -0,0 +1,5 @@
+---
+"@apollo/client": patch
+---
+
+Fix a regression where rerendering a component with `useBackgroundQuery` would recreate the `queryRef` instance when used with React's strict mode.

From 24d89ca8f9c200a04eccbf75724c5472bf86a0b7 Mon Sep 17 00:00:00 2001
From: Jerel Miller <jerelmiller@gmail.com>
Date: Tue, 30 Apr 2024 17:57:23 -0600
Subject: [PATCH 04/10] Update API report

---
 .api-reports/api-report-react.md          | 8 ++++++--
 .api-reports/api-report-react_hooks.md    | 8 ++++++--
 .api-reports/api-report-react_internal.md | 6 ++++--
 .api-reports/api-report.md                | 8 ++++++--
 4 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/.api-reports/api-report-react.md b/.api-reports/api-report-react.md
index 9058ec39286..501d03ad943 100644
--- a/.api-reports/api-report-react.md
+++ b/.api-reports/api-report-react.md
@@ -959,11 +959,17 @@ class InternalQueryReference<TData = unknown> {
     // (undocumented)
     softRetain(): () => void;
     // (undocumented)
+    strictModeFixAddToSuspenseCache(): void;
+    // (undocumented)
     get watchQueryOptions(): WatchQueryOptions<OperationVariables, TData>;
 }
 
 // @public (undocumented)
 interface InternalQueryReferenceOptions {
+    // Warning: (ae-forgotten-export) The symbol "InternalQueryReference" needs to be exported by the entry point index.d.ts
+    //
+    // (undocumented)
+    addToSuspenseCache?: (queryRef: InternalQueryReference<any>) => void;
     // (undocumented)
     autoDisposeTimeoutMs?: number;
     // (undocumented)
@@ -1756,8 +1762,6 @@ interface QueryOptions<TVariables = OperationVariables, TData = any> {
 export interface QueryReference<TData = unknown, TVariables = unknown> {
     // @internal (undocumented)
     [PROMISE_SYMBOL]: QueryRefPromise<TData>;
-    // Warning: (ae-forgotten-export) The symbol "InternalQueryReference" needs to be exported by the entry point index.d.ts
-    //
     // @internal (undocumented)
     readonly [QUERY_REFERENCE_SYMBOL]: InternalQueryReference<TData>;
     toPromise(): Promise<QueryReference<TData, TVariables>>;
diff --git a/.api-reports/api-report-react_hooks.md b/.api-reports/api-report-react_hooks.md
index e5ba909b727..71e5af5477b 100644
--- a/.api-reports/api-report-react_hooks.md
+++ b/.api-reports/api-report-react_hooks.md
@@ -901,11 +901,17 @@ class InternalQueryReference<TData = unknown> {
     // (undocumented)
     softRetain(): () => void;
     // (undocumented)
+    strictModeFixAddToSuspenseCache(): void;
+    // (undocumented)
     get watchQueryOptions(): WatchQueryOptions<OperationVariables, TData>;
 }
 
 // @public (undocumented)
 interface InternalQueryReferenceOptions {
+    // Warning: (ae-forgotten-export) The symbol "InternalQueryReference" needs to be exported by the entry point index.d.ts
+    //
+    // (undocumented)
+    addToSuspenseCache?: (queryRef: InternalQueryReference<any>) => void;
     // (undocumented)
     autoDisposeTimeoutMs?: number;
     // (undocumented)
@@ -1631,8 +1637,6 @@ interface QueryOptions<TVariables = OperationVariables, TData = any> {
 interface QueryReference<TData = unknown, TVariables = unknown> {
     // @internal (undocumented)
     [PROMISE_SYMBOL]: QueryRefPromise<TData>;
-    // Warning: (ae-forgotten-export) The symbol "InternalQueryReference" needs to be exported by the entry point index.d.ts
-    //
     // @internal (undocumented)
     readonly [QUERY_REFERENCE_SYMBOL]: InternalQueryReference<TData>;
     toPromise(): Promise<QueryReference<TData, TVariables>>;
diff --git a/.api-reports/api-report-react_internal.md b/.api-reports/api-report-react_internal.md
index 69aa5c05e09..22031bad0cf 100644
--- a/.api-reports/api-report-react_internal.md
+++ b/.api-reports/api-report-react_internal.md
@@ -891,11 +891,15 @@ export class InternalQueryReference<TData = unknown> {
     // (undocumented)
     softRetain(): () => void;
     // (undocumented)
+    strictModeFixAddToSuspenseCache(): void;
+    // (undocumented)
     get watchQueryOptions(): WatchQueryOptions<OperationVariables, TData>;
 }
 
 // @public (undocumented)
 interface InternalQueryReferenceOptions {
+    // (undocumented)
+    addToSuspenseCache?: (queryRef: InternalQueryReference<any>) => void;
     // (undocumented)
     autoDisposeTimeoutMs?: number;
     // (undocumented)
@@ -1737,8 +1741,6 @@ interface SubscriptionOptions<TVariables = OperationVariables, TData = any> {
 class SuspenseCache {
     constructor(options?: SuspenseCacheOptions);
     // (undocumented)
-    add(cacheKey: CacheKey, queryRef: InternalQueryReference<unknown>): void;
-    // (undocumented)
     getQueryRef<TData = any>(cacheKey: CacheKey, createObservable: () => ObservableQuery<TData>): InternalQueryReference<TData>;
 }
 
diff --git a/.api-reports/api-report.md b/.api-reports/api-report.md
index 9b72b6ae88b..5bd4088b017 100644
--- a/.api-reports/api-report.md
+++ b/.api-reports/api-report.md
@@ -1332,11 +1332,17 @@ class InternalQueryReference<TData = unknown> {
     // (undocumented)
     softRetain(): () => void;
     // (undocumented)
+    strictModeFixAddToSuspenseCache(): void;
+    // (undocumented)
     get watchQueryOptions(): WatchQueryOptions<OperationVariables, TData>;
 }
 
 // @public (undocumented)
 interface InternalQueryReferenceOptions {
+    // Warning: (ae-forgotten-export) The symbol "InternalQueryReference" needs to be exported by the entry point index.d.ts
+    //
+    // (undocumented)
+    addToSuspenseCache?: (queryRef: InternalQueryReference<any>) => void;
     // (undocumented)
     autoDisposeTimeoutMs?: number;
     // (undocumented)
@@ -2327,8 +2333,6 @@ export { QueryOptions }
 export interface QueryReference<TData = unknown, TVariables = unknown> {
     // @internal (undocumented)
     [PROMISE_SYMBOL]: QueryRefPromise<TData>;
-    // Warning: (ae-forgotten-export) The symbol "InternalQueryReference" needs to be exported by the entry point index.d.ts
-    //
     // @internal (undocumented)
     readonly [QUERY_REFERENCE_SYMBOL]: InternalQueryReference<TData>;
     toPromise(): Promise<QueryReference<TData, TVariables>>;

From 38efc53ace45290001a9ab48dac6250a468d21c8 Mon Sep 17 00:00:00 2001
From: Jerel Miller <jerelmiller@gmail.com>
Date: Tue, 30 Apr 2024 17:57:39 -0600
Subject: [PATCH 05/10] Update size limits

---
 .size-limits.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.size-limits.json b/.size-limits.json
index b18103fdaec..45dcdd441b8 100644
--- a/.size-limits.json
+++ b/.size-limits.json
@@ -1,4 +1,4 @@
 {
-  "dist/apollo-client.min.cjs": 39551,
+  "dist/apollo-client.min.cjs": 39614,
   "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32826
 }

From c4bf002d483f4e0c99de6fa2eacd06c84c8cc0a8 Mon Sep 17 00:00:00 2001
From: Jerel Miller <jerelmiller@gmail.com>
Date: Thu, 2 May 2024 15:27:46 -0600
Subject: [PATCH 06/10] Wrap dispose check in setTimeout in useBackgroundQuery

---
 src/react/hooks/useBackgroundQuery.ts      | 23 +++++++++++-------
 src/react/hooks/useReadQuery.ts            | 13 +---------
 src/react/hooks/useSuspenseQuery.ts        | 28 ----------------------
 src/react/internal/cache/QueryReference.ts | 14 ++++-------
 src/react/internal/cache/SuspenseCache.ts  |  9 +++----
 5 files changed, 25 insertions(+), 62 deletions(-)

diff --git a/src/react/hooks/useBackgroundQuery.ts b/src/react/hooks/useBackgroundQuery.ts
index e07ecac873a..601273ce627 100644
--- a/src/react/hooks/useBackgroundQuery.ts
+++ b/src/react/hooks/useBackgroundQuery.ts
@@ -239,16 +239,21 @@ function _useBackgroundQuery<
     updateWrappedQueryRef(wrappedQueryRef, promise);
   }
 
-  // Handle strict mode where the query ref might be disposed when useEffect
-  // runs twice. We add the queryRef back in the suspense cache so that the next
-  // render will reuse this queryRef rather than initializing a new instance.
-  // This also prevents issues where rerendering useBackgroundQuery after the
-  // queryRef has been disposed, either automatically or by unmounting
-  // useReadQuery will ensure the same queryRef is maintained.
+  // This prevents issues where rerendering useBackgroundQuery after the
+  // queryRef has been disposed would cause the hook to return a new queryRef
+  // instance since disposal also removes it from the suspense cache. We add
+  // the queryRef back in the suspense cache so that the next render will reuse
+  // this queryRef rather than initializing a new instance.
   React.useEffect(() => {
-    if (queryRef.disposed) {
-      queryRef.strictModeFixAddToSuspenseCache();
-    }
+    // Since the queryRef is disposed async via `setTimeout`, we have to wait a
+    // tick before checking it and adding back to the suspense cache.
+    const id = setTimeout(() => {
+      if (queryRef.disposed) {
+        suspenseCache.add(cacheKey, queryRef);
+      }
+    });
+
+    return () => clearTimeout(id);
     // Omitting the deps is intentional. This avoids stale closures and the
     // conditional ensures we aren't running the logic on each render.
   });
diff --git a/src/react/hooks/useReadQuery.ts b/src/react/hooks/useReadQuery.ts
index 681a40660a4..0b600c59f34 100644
--- a/src/react/hooks/useReadQuery.ts
+++ b/src/react/hooks/useReadQuery.ts
@@ -83,18 +83,7 @@ function _useReadQuery<TData>(
     updateWrappedQueryRef(queryRef, internalQueryRef.promise);
   }
 
-  React.useEffect(() => {
-    // It may seem odd that we are trying to reinitialize the queryRef even
-    // though we reinitialize in render above, but this is necessary to
-    // handle strict mode where this useEffect will be run twice resulting in a
-    // disposed queryRef before the next render.
-    if (internalQueryRef.disposed) {
-      internalQueryRef.strictModeFixAddToSuspenseCache();
-      internalQueryRef.reinitialize();
-    }
-
-    return internalQueryRef.retain();
-  }, [internalQueryRef]);
+  React.useEffect(() => internalQueryRef.retain(), [internalQueryRef]);
 
   const promise = useSyncExternalStore(
     React.useCallback(
diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts
index e6b252d470f..77159eb31f2 100644
--- a/src/react/hooks/useSuspenseQuery.ts
+++ b/src/react/hooks/useSuspenseQuery.ts
@@ -239,34 +239,6 @@ function _useSuspenseQuery<
     };
   }, [queryRef]);
 
-  // This effect handles the case where strict mode causes the queryRef to get
-  // disposed early. Previously this was done by using a `setTimeout` inside the
-  // dispose function, but this could cause some issues in cases where someone
-  // might expect the queryRef to be disposed immediately. For example, when
-  // using the same client instance across multiple tests in a test suite, the
-  // `setTimeout` has the possibility of retaining the suspense cache entry for
-  // too long, which means that two tests might accidentally share the same
-  // `queryRef` instance. By immediately disposing, we can avoid this situation.
-  //
-  // Instead we can leverage the work done to allow the queryRef to "resume"
-  // after it has been disposed without executing an additional network request.
-  // This is done by calling the `initialize` function below.
-  React.useEffect(() => {
-    if (queryRef.disposed) {
-      // Calling the `dispose` function removes it from the suspense cache, so
-      // when the component rerenders, it instantiates a fresh query ref.
-      // We address this by adding the queryRef back to the suspense cache
-      // so that the lookup on the next render uses the existing queryRef rather
-      // than instantiating a new one.
-      queryRef.strictModeFixAddToSuspenseCache();
-      queryRef.reinitialize();
-    }
-    // We can omit the deps here to get a fresh closure each render since the
-    // conditional will prevent the logic from running in most cases. This
-    // should also be a touch faster since it should be faster to check the `if`
-    // statement than for React to compare deps on this effect.
-  });
-
   const skipResult = React.useMemo(() => {
     const error = toApolloError(queryRef.result);
 
diff --git a/src/react/internal/cache/QueryReference.ts b/src/react/internal/cache/QueryReference.ts
index 2f933526119..b382b5ca221 100644
--- a/src/react/internal/cache/QueryReference.ts
+++ b/src/react/internal/cache/QueryReference.ts
@@ -80,7 +80,6 @@ export interface QueryReference<TData = unknown, TVariables = unknown> {
 
 interface InternalQueryReferenceOptions {
   onDispose?: () => void;
-  addToSuspenseCache?: (queryRef: InternalQueryReference<any>) => void;
   autoDisposeTimeoutMs?: number;
 }
 
@@ -172,7 +171,6 @@ export class InternalQueryReference<TData = unknown> {
     this.handleError = this.handleError.bind(this);
     this.dispose = this.dispose.bind(this);
     this.observable = observable;
-    this.addToSuspenseCache = options.addToSuspenseCache;
 
     if (options.onDispose) {
       this.onDispose = options.onDispose;
@@ -208,10 +206,6 @@ export class InternalQueryReference<TData = unknown> {
     return this.observable.options;
   }
 
-  strictModeFixAddToSuspenseCache() {
-    this.addToSuspenseCache?.(this);
-  }
-
   reinitialize() {
     const { observable } = this;
 
@@ -253,9 +247,11 @@ export class InternalQueryReference<TData = unknown> {
       disposed = true;
       this.references--;
 
-      if (!this.references) {
-        this.dispose();
-      }
+      setTimeout(() => {
+        if (!this.references) {
+          this.dispose();
+        }
+      });
     };
   }
 
diff --git a/src/react/internal/cache/SuspenseCache.ts b/src/react/internal/cache/SuspenseCache.ts
index 4ef90f18485..8b1eba321b5 100644
--- a/src/react/internal/cache/SuspenseCache.ts
+++ b/src/react/internal/cache/SuspenseCache.ts
@@ -39,10 +39,6 @@ export class SuspenseCache {
     if (!ref.current) {
       ref.current = new InternalQueryReference(createObservable(), {
         autoDisposeTimeoutMs: this.options.autoDisposeTimeoutMs,
-        addToSuspenseCache: (queryRef) => {
-          const ref = this.queryRefs.lookupArray(cacheKey);
-          ref.current = queryRef;
-        },
         onDispose: () => {
           delete ref.current;
         },
@@ -51,4 +47,9 @@ export class SuspenseCache {
 
     return ref.current;
   }
+
+  add(cacheKey: CacheKey, queryRef: InternalQueryReference<unknown>) {
+    const ref = this.queryRefs.lookupArray(cacheKey);
+    ref.current = queryRef;
+  }
 }

From bedd9377ad3cf80f3cd0e08f0e50f39c9d55586b Mon Sep 17 00:00:00 2001
From: Jerel Miller <jerelmiller@gmail.com>
Date: Thu, 2 May 2024 15:30:05 -0600
Subject: [PATCH 07/10] Remove unneeded property from QueryReference

---
 src/react/internal/cache/QueryReference.ts | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/react/internal/cache/QueryReference.ts b/src/react/internal/cache/QueryReference.ts
index b382b5ca221..16a014d0d89 100644
--- a/src/react/internal/cache/QueryReference.ts
+++ b/src/react/internal/cache/QueryReference.ts
@@ -147,9 +147,6 @@ export class InternalQueryReference<TData = unknown> {
   public result!: ApolloQueryResult<TData>;
   public readonly key: QueryKey = {};
   public readonly observable: ObservableQuery<TData>;
-  private readonly addToSuspenseCache?: (
-    queryRef: InternalQueryReference<TData>
-  ) => void;
 
   public promise!: QueryRefPromise<TData>;
 

From d727d46aa91e59834e1ce40ee6c1489074422919 Mon Sep 17 00:00:00 2001
From: Jerel Miller <jerelmiller@gmail.com>
Date: Thu, 2 May 2024 15:31:14 -0600
Subject: [PATCH 08/10] Rerun api report

---
 .api-reports/api-report-react.md          | 8 ++------
 .api-reports/api-report-react_hooks.md    | 8 ++------
 .api-reports/api-report-react_internal.md | 6 ++----
 .api-reports/api-report.md                | 8 ++------
 4 files changed, 8 insertions(+), 22 deletions(-)

diff --git a/.api-reports/api-report-react.md b/.api-reports/api-report-react.md
index 501d03ad943..9058ec39286 100644
--- a/.api-reports/api-report-react.md
+++ b/.api-reports/api-report-react.md
@@ -959,17 +959,11 @@ class InternalQueryReference<TData = unknown> {
     // (undocumented)
     softRetain(): () => void;
     // (undocumented)
-    strictModeFixAddToSuspenseCache(): void;
-    // (undocumented)
     get watchQueryOptions(): WatchQueryOptions<OperationVariables, TData>;
 }
 
 // @public (undocumented)
 interface InternalQueryReferenceOptions {
-    // Warning: (ae-forgotten-export) The symbol "InternalQueryReference" needs to be exported by the entry point index.d.ts
-    //
-    // (undocumented)
-    addToSuspenseCache?: (queryRef: InternalQueryReference<any>) => void;
     // (undocumented)
     autoDisposeTimeoutMs?: number;
     // (undocumented)
@@ -1762,6 +1756,8 @@ interface QueryOptions<TVariables = OperationVariables, TData = any> {
 export interface QueryReference<TData = unknown, TVariables = unknown> {
     // @internal (undocumented)
     [PROMISE_SYMBOL]: QueryRefPromise<TData>;
+    // Warning: (ae-forgotten-export) The symbol "InternalQueryReference" needs to be exported by the entry point index.d.ts
+    //
     // @internal (undocumented)
     readonly [QUERY_REFERENCE_SYMBOL]: InternalQueryReference<TData>;
     toPromise(): Promise<QueryReference<TData, TVariables>>;
diff --git a/.api-reports/api-report-react_hooks.md b/.api-reports/api-report-react_hooks.md
index 71e5af5477b..e5ba909b727 100644
--- a/.api-reports/api-report-react_hooks.md
+++ b/.api-reports/api-report-react_hooks.md
@@ -901,17 +901,11 @@ class InternalQueryReference<TData = unknown> {
     // (undocumented)
     softRetain(): () => void;
     // (undocumented)
-    strictModeFixAddToSuspenseCache(): void;
-    // (undocumented)
     get watchQueryOptions(): WatchQueryOptions<OperationVariables, TData>;
 }
 
 // @public (undocumented)
 interface InternalQueryReferenceOptions {
-    // Warning: (ae-forgotten-export) The symbol "InternalQueryReference" needs to be exported by the entry point index.d.ts
-    //
-    // (undocumented)
-    addToSuspenseCache?: (queryRef: InternalQueryReference<any>) => void;
     // (undocumented)
     autoDisposeTimeoutMs?: number;
     // (undocumented)
@@ -1637,6 +1631,8 @@ interface QueryOptions<TVariables = OperationVariables, TData = any> {
 interface QueryReference<TData = unknown, TVariables = unknown> {
     // @internal (undocumented)
     [PROMISE_SYMBOL]: QueryRefPromise<TData>;
+    // Warning: (ae-forgotten-export) The symbol "InternalQueryReference" needs to be exported by the entry point index.d.ts
+    //
     // @internal (undocumented)
     readonly [QUERY_REFERENCE_SYMBOL]: InternalQueryReference<TData>;
     toPromise(): Promise<QueryReference<TData, TVariables>>;
diff --git a/.api-reports/api-report-react_internal.md b/.api-reports/api-report-react_internal.md
index 22031bad0cf..69aa5c05e09 100644
--- a/.api-reports/api-report-react_internal.md
+++ b/.api-reports/api-report-react_internal.md
@@ -891,15 +891,11 @@ export class InternalQueryReference<TData = unknown> {
     // (undocumented)
     softRetain(): () => void;
     // (undocumented)
-    strictModeFixAddToSuspenseCache(): void;
-    // (undocumented)
     get watchQueryOptions(): WatchQueryOptions<OperationVariables, TData>;
 }
 
 // @public (undocumented)
 interface InternalQueryReferenceOptions {
-    // (undocumented)
-    addToSuspenseCache?: (queryRef: InternalQueryReference<any>) => void;
     // (undocumented)
     autoDisposeTimeoutMs?: number;
     // (undocumented)
@@ -1741,6 +1737,8 @@ interface SubscriptionOptions<TVariables = OperationVariables, TData = any> {
 class SuspenseCache {
     constructor(options?: SuspenseCacheOptions);
     // (undocumented)
+    add(cacheKey: CacheKey, queryRef: InternalQueryReference<unknown>): void;
+    // (undocumented)
     getQueryRef<TData = any>(cacheKey: CacheKey, createObservable: () => ObservableQuery<TData>): InternalQueryReference<TData>;
 }
 
diff --git a/.api-reports/api-report.md b/.api-reports/api-report.md
index 5bd4088b017..9b72b6ae88b 100644
--- a/.api-reports/api-report.md
+++ b/.api-reports/api-report.md
@@ -1332,17 +1332,11 @@ class InternalQueryReference<TData = unknown> {
     // (undocumented)
     softRetain(): () => void;
     // (undocumented)
-    strictModeFixAddToSuspenseCache(): void;
-    // (undocumented)
     get watchQueryOptions(): WatchQueryOptions<OperationVariables, TData>;
 }
 
 // @public (undocumented)
 interface InternalQueryReferenceOptions {
-    // Warning: (ae-forgotten-export) The symbol "InternalQueryReference" needs to be exported by the entry point index.d.ts
-    //
-    // (undocumented)
-    addToSuspenseCache?: (queryRef: InternalQueryReference<any>) => void;
     // (undocumented)
     autoDisposeTimeoutMs?: number;
     // (undocumented)
@@ -2333,6 +2327,8 @@ export { QueryOptions }
 export interface QueryReference<TData = unknown, TVariables = unknown> {
     // @internal (undocumented)
     [PROMISE_SYMBOL]: QueryRefPromise<TData>;
+    // Warning: (ae-forgotten-export) The symbol "InternalQueryReference" needs to be exported by the entry point index.d.ts
+    //
     // @internal (undocumented)
     readonly [QUERY_REFERENCE_SYMBOL]: InternalQueryReference<TData>;
     toPromise(): Promise<QueryReference<TData, TVariables>>;

From d1e61d64645079ec40c6d97504d23c1fabfdc563 Mon Sep 17 00:00:00 2001
From: Jerel Miller <jerelmiller@gmail.com>
Date: Thu, 2 May 2024 15:35:00 -0600
Subject: [PATCH 09/10] Add additional changeset for revert of behavior

---
 .changeset/seven-forks-own.md | 6 ++++++
 1 file changed, 6 insertions(+)
 create mode 100644 .changeset/seven-forks-own.md

diff --git a/.changeset/seven-forks-own.md b/.changeset/seven-forks-own.md
new file mode 100644
index 00000000000..a8c779efa57
--- /dev/null
+++ b/.changeset/seven-forks-own.md
@@ -0,0 +1,6 @@
+---
+"@apollo/client": patch
+---
+
+Revert the change introduced in
+[3.9.10](https://github.com/apollographql/apollo-client/releases/tag/v3.9.10) via #11738 that disposed of queryRefs synchronously. This change caused too many issues with strict mode.

From 7d5ff9f08f61645008e4014d2b81d4388c1f0a95 Mon Sep 17 00:00:00 2001
From: jerelmiller <jerelmiller@users.noreply.github.com>
Date: Fri, 3 May 2024 15:40:23 +0000
Subject: [PATCH 10/10] Clean up Prettier, Size-limit, and Api-Extractor

---
 .size-limits.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.size-limits.json b/.size-limits.json
index 45dcdd441b8..855bf336313 100644
--- a/.size-limits.json
+++ b/.size-limits.json
@@ -1,4 +1,4 @@
 {
-  "dist/apollo-client.min.cjs": 39614,
+  "dist/apollo-client.min.cjs": 39540,
   "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32826
 }