diff --git a/.changeset/eleven-bikes-flash.md b/.changeset/eleven-bikes-flash.md
new file mode 100644
index 00000000000..c68d174d1d4
--- /dev/null
+++ b/.changeset/eleven-bikes-flash.md
@@ -0,0 +1,5 @@
+---
+"@apollo/client": patch
+---
+
+Fix an issue where switching from options with `variables` to `skipToken` with `useSuspenseQuery` and `useBackgroundQuery` would create a new `ObservableQuery`. This could cause unintended refetches where `variables` were absent in the request when the query was referenced with `refetchQueries`.
diff --git a/.size-limits.json b/.size-limits.json
index abf6288b711..4f116ec9282 100644
--- a/.size-limits.json
+++ b/.size-limits.json
@@ -1,6 +1,6 @@
{
- "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (CJS)": 43812,
- "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production) (CJS)": 38745,
- "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\"": 33456,
- "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production)": 27523
+ "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (CJS)": 43882,
+ "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production) (CJS)": 38754,
+ "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\"": 33430,
+ "import { ApolloClient, InMemoryCache, HttpLink } from \"@apollo/client\" (production)": 27518
}
diff --git a/ROADMAP.md b/ROADMAP.md
index f5b60a4a293..1a3cc5ac991 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -18,6 +18,7 @@ For up to date release notes, refer to the project's [Changelog](https://github.
### Apollo Client
#### 4.1.0
+
_Release candidate - November 14th, 2025_
- Support for `@stream`
diff --git a/config/jest.config.ts b/config/jest.config.ts
index 9e8be6190dc..b7996a9a3b5 100644
--- a/config/jest.config.ts
+++ b/config/jest.config.ts
@@ -39,17 +39,23 @@ const ignoreDTSFiles = ".d.ts$";
const ignoreTSFiles = ".ts$";
const ignoreTSXFiles = ".tsx$";
-const react19TestFileIgnoreList = [ignoreDTSFiles, ignoreTSFiles];
-
-const react17TestFileIgnoreList = [
+const reactSharedTestFileIgnoreList = [
ignoreDTSFiles,
ignoreTSFiles,
+ "src/react/hooks/__tests__/useBackgroundQuery/testUtils.tsx",
+ "src/react/hooks/__tests__/useSuspenseQuery/testUtils.tsx",
+];
+
+const react17TestFileIgnoreList = [
+ ...reactSharedTestFileIgnoreList,
// We only support Suspense with React 18, so don't test suspense hooks with
// React 17
"src/testing/experimental/__tests__/createTestSchema.test.tsx",
"src/react/hooks/__tests__/useSuspenseFragment.test.tsx",
"src/react/hooks/__tests__/useSuspenseQuery.test.tsx",
+ "src/react/hooks/__tests__/useSuspenseQuery/*",
"src/react/hooks/__tests__/useBackgroundQuery.test.tsx",
+ "src/react/hooks/__tests__/useBackgroundQuery/*",
"src/react/hooks/__tests__/useLoadableQuery.test.tsx",
"src/react/hooks/__tests__/useQueryRefHandlers.test.tsx",
"src/react/query-preloader/__tests__/createQueryPreloader.test.tsx",
@@ -81,15 +87,14 @@ const tsRxJSMinConfig = {
const standardReact19Config = {
...defaults,
displayName: "ReactDOM 19",
- testPathIgnorePatterns: react19TestFileIgnoreList,
+ testPathIgnorePatterns: reactSharedTestFileIgnoreList,
};
const standardReact18Config = {
...defaults,
displayName: "ReactDOM 18",
testPathIgnorePatterns: [
- ignoreDTSFiles,
- ignoreTSFiles,
+ ...reactSharedTestFileIgnoreList,
"src/react/ssr/__tests__/prerenderStatic.test.tsx",
],
moduleNameMapper: {
diff --git a/package-lock.json b/package-lock.json
index 3b31b717d01..89c275a5ddb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -48,7 +48,7 @@
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.1.0",
- "@testing-library/react-render-stream": "2.0.0",
+ "@testing-library/react-render-stream": "2.0.2",
"@testing-library/user-event": "14.5.2",
"@types/babel__preset-env": "^7.10.0",
"@types/bytes": "3.1.4",
@@ -6189,7 +6189,9 @@
}
},
"node_modules/@testing-library/react-render-stream": {
- "version": "2.0.0",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@testing-library/react-render-stream/-/react-render-stream-2.0.2.tgz",
+ "integrity": "sha512-rZNWU6ECbqaplYoxxaD5+l4NRX49qxzCzZ8Sjbetw5JADchYAe+8h+TDy7G+1sAIUwevWbthE2sQfXyVfkTExg==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index be5b0f1b757..cd6a327a6b5 100644
--- a/package.json
+++ b/package.json
@@ -180,7 +180,7 @@
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.1.0",
- "@testing-library/react-render-stream": "2.0.0",
+ "@testing-library/react-render-stream": "2.0.2",
"@testing-library/user-event": "14.5.2",
"@types/babel__preset-env": "^7.10.0",
"@types/bytes": "3.1.4",
diff --git a/src/react/hooks/__tests__/useBackgroundQuery/skipToken.test.tsx b/src/react/hooks/__tests__/useBackgroundQuery/skipToken.test.tsx
new file mode 100644
index 00000000000..f70e6e8e414
--- /dev/null
+++ b/src/react/hooks/__tests__/useBackgroundQuery/skipToken.test.tsx
@@ -0,0 +1,293 @@
+import { disableActEnvironment } from "@testing-library/react-render-stream";
+import React from "react";
+import { delay, of } from "rxjs";
+
+import {
+ ApolloClient,
+ ApolloLink,
+ InMemoryCache,
+ NetworkStatus,
+} from "@apollo/client";
+import { skipToken, useBackgroundQuery } from "@apollo/client/react";
+import {
+ createClientWrapper,
+ createMockWrapper,
+ setupVariablesCase,
+} from "@apollo/client/testing/internal";
+
+import { renderUseBackgroundQuery } from "./testUtils.js";
+
+// https://github.com/apollographql/apollo-client/issues/12989
+test("maintains variables when switching to `skipToken` and calling `refetchQueries` while skipped after initial request", async () => {
+ const { query } = setupVariablesCase();
+
+ const client = new ApolloClient({
+ link: new ApolloLink((operation) => {
+ return of(
+ operation.variables.id === "1" ?
+ {
+ data: {
+ character: {
+ __typename: "Character",
+ id: "1",
+ name: "Spider-Man",
+ },
+ },
+ }
+ : {
+ data: null,
+ errors: [
+ { message: `Fetched wrong id: ${operation.variables.id}` },
+ ],
+ }
+ ).pipe(delay(10));
+ }),
+ cache: new InMemoryCache(),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { rerender, takeRender } = await renderUseBackgroundQuery(
+ ({ id }) =>
+ useBackgroundQuery(
+ query,
+ id === undefined ? skipToken : { variables: { id } }
+ ),
+ {
+ initialProps: { id: "1" as string | undefined },
+ wrapper: createClientWrapper(client),
+ }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "1", name: "Spider-Man" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await rerender({ id: undefined });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "useReadQuery",
+ ]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "1", name: "Spider-Man" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+
+ await expect(
+ client.refetchQueries({ include: [query] })
+ ).resolves.toStrictEqualTyped([
+ {
+ data: {
+ character: { __typename: "Character", id: "1", name: "Spider-Man" },
+ },
+ },
+ ]);
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("suspends and fetches when changing variables when no longer using skipToken", async () => {
+ const { query, mocks } = setupVariablesCase({
+ delay: React.version.startsWith("18") ? 200 : 20,
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { rerender, takeRender } = await renderUseBackgroundQuery(
+ ({ id }) =>
+ useBackgroundQuery(
+ query,
+ id === undefined ? skipToken : { variables: { id } }
+ ),
+ {
+ initialProps: { id: "1" as string | undefined },
+ wrapper: createMockWrapper({ mocks }),
+ }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "1", name: "Spider-Man" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await rerender({ id: undefined });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "useReadQuery",
+ ]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "1", name: "Spider-Man" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await rerender({ id: "2" });
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
+
+ {
+ const { snapshot } = await takeRender();
+
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "2", name: "Black Widow" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("does not suspend for data in the cache when changing variables when no longer using skipToken", async () => {
+ const { query, mocks } = setupVariablesCase();
+
+ const cache = new InMemoryCache();
+
+ cache.writeQuery({
+ query,
+ data: {
+ character: { __typename: "Character", id: "2", name: "Cached Widow" },
+ },
+ variables: { id: "2" },
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { rerender, takeRender } = await renderUseBackgroundQuery(
+ ({ id }) =>
+ useBackgroundQuery(
+ query,
+ id === undefined ? skipToken : { variables: { id } }
+ ),
+ {
+ initialProps: { id: "1" as string | undefined },
+ wrapper: createMockWrapper({ cache, mocks }),
+ }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "",
+ ]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useReadQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "1", name: "Spider-Man" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await rerender({ id: undefined });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "useReadQuery",
+ ]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "1", name: "Spider-Man" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await rerender({ id: "2" });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([
+ "useBackgroundQuery",
+ "useReadQuery",
+ ]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "2", name: "Cached Widow" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
diff --git a/src/react/hooks/__tests__/useBackgroundQuery/testUtils.tsx b/src/react/hooks/__tests__/useBackgroundQuery/testUtils.tsx
new file mode 100644
index 00000000000..e1d34750a5b
--- /dev/null
+++ b/src/react/hooks/__tests__/useBackgroundQuery/testUtils.tsx
@@ -0,0 +1,75 @@
+import type { RenderOptions } from "@testing-library/react";
+import {
+ createRenderStream,
+ useTrackRenders,
+} from "@testing-library/react-render-stream";
+import React, { Suspense } from "react";
+import { ErrorBoundary } from "react-error-boundary";
+
+import type { DataState, ErrorLike, OperationVariables } from "@apollo/client";
+import type { QueryRef, useBackgroundQuery } from "@apollo/client/react";
+import { useReadQuery } from "@apollo/client/react";
+
+export async function renderUseBackgroundQuery<
+ TData,
+ TVariables extends OperationVariables,
+ TQueryRef extends QueryRef,
+ TStates extends DataState["dataState"] = TQueryRef extends (
+ QueryRef
+ ) ?
+ States
+ : never,
+ Props = never,
+>(
+ renderHook: (
+ props: Props extends never ? undefined : Props
+ ) => [TQueryRef | undefined, useBackgroundQuery.Result],
+ options: Pick & { initialProps?: Props }
+) {
+ function UseReadQuery({ queryRef }: { queryRef: QueryRef }) {
+ useTrackRenders({ name: "useReadQuery" });
+ replaceSnapshot(useReadQuery(queryRef) as any);
+
+ return null;
+ }
+
+ function SuspenseFallback() {
+ useTrackRenders({ name: "" });
+
+ return null;
+ }
+
+ function ErrorFallback() {
+ useTrackRenders({ name: "" });
+
+ return null;
+ }
+
+ function App({ props }: { props: Props | undefined }) {
+ useTrackRenders({ name: "useBackgroundQuery" });
+ const [queryRef] = renderHook(props as any);
+
+ return (
+ }>
+ replaceSnapshot({ error })}
+ >
+ {queryRef && }
+
+
+ );
+ }
+
+ const { render, takeRender, replaceSnapshot } = createRenderStream<
+ useReadQuery.Result | { error: ErrorLike }
+ >();
+
+ const utils = await render(, options);
+
+ function rerender(props: Props) {
+ return utils.rerender();
+ }
+
+ return { takeRender, rerender };
+}
diff --git a/src/react/hooks/__tests__/useLazyQuery.test.tsx b/src/react/hooks/__tests__/useLazyQuery.test.tsx
index 978577b2cb2..fb5b2b2b570 100644
--- a/src/react/hooks/__tests__/useLazyQuery.test.tsx
+++ b/src/react/hooks/__tests__/useLazyQuery.test.tsx
@@ -2834,8 +2834,7 @@ describe("useLazyQuery Hook", () => {
const [originalExecute] = getCurrentSnapshot();
countRef.current++;
- // TODO: Update when https://github.com/testing-library/react-render-stream-testing-library/issues/13 is fixed
- await rerender(undefined);
+ await rerender();
{
const [, result] = await takeSnapshot();
@@ -2890,8 +2889,7 @@ describe("useLazyQuery Hook", () => {
countRef.current++;
- // TODO: Update when https://github.com/testing-library/react-render-stream-testing-library/issues/13 is fixed
- await rerender(undefined);
+ await rerender();
{
const [, result] = await takeSnapshot();
@@ -2950,8 +2948,7 @@ describe("useLazyQuery Hook", () => {
trackClosureValue.mockClear();
countRef.current++;
- // TODO: Update when https://github.com/testing-library/react-render-stream-testing-library/issues/13 is fixed
- await rerender(undefined);
+ await rerender();
[execute] = getCurrentSnapshot();
expect(execute).toBe(originalExecute);
diff --git a/src/react/hooks/__tests__/useQuery.test.tsx b/src/react/hooks/__tests__/useQuery.test.tsx
index 2d544bb11ac..f038bcfdb5a 100644
--- a/src/react/hooks/__tests__/useQuery.test.tsx
+++ b/src/react/hooks/__tests__/useQuery.test.tsx
@@ -174,7 +174,7 @@ describe("useQuery Hook", () => {
});
}
- await rerender({ children: null });
+ await rerender();
{
const result = await takeSnapshot();
@@ -244,7 +244,7 @@ describe("useQuery Hook", () => {
});
}
- await rerender({ children: null });
+ await rerender();
{
const result = await takeSnapshot();
@@ -400,7 +400,7 @@ describe("useQuery Hook", () => {
});
}
- await rerender(undefined);
+ await rerender();
{
const result = await takeSnapshot();
@@ -471,7 +471,7 @@ describe("useQuery Hook", () => {
expect(subscribeToMore).toBe(result.subscribeToMore);
}
- await rerender(undefined);
+ await rerender();
{
const result = await takeSnapshot();
@@ -1244,7 +1244,7 @@ describe("useQuery Hook", () => {
});
}
- await rerender({});
+ await rerender();
{
const [result0, result1] = await takeSnapshot();
@@ -4064,7 +4064,7 @@ describe("useQuery Hook", () => {
});
}
- await rerender(undefined);
+ await rerender();
{
const result = await takeSnapshot();
@@ -8103,7 +8103,7 @@ describe("useQuery Hook", () => {
variables: {},
});
- await rerender(undefined);
+ await rerender();
await expect(takeSnapshot()).resolves.toStrictEqualTyped({
data: undefined,
diff --git a/src/react/hooks/__tests__/useSuspenseQuery.test.tsx b/src/react/hooks/__tests__/useSuspenseQuery.test.tsx
index 7122e12171a..6ddb4ce4d32 100644
--- a/src/react/hooks/__tests__/useSuspenseQuery.test.tsx
+++ b/src/react/hooks/__tests__/useSuspenseQuery.test.tsx
@@ -8,11 +8,12 @@ import {
import { userEvent } from "@testing-library/user-event";
import { equal } from "@wry/equality";
import { expectTypeOf } from "expect-type";
+import type { GraphQLFormattedError } from "graphql";
import { GraphQLError } from "graphql";
import React, { Fragment, StrictMode, Suspense, useTransition } from "react";
import type { FallbackProps } from "react-error-boundary";
import { ErrorBoundary } from "react-error-boundary";
-import { Observable, of } from "rxjs";
+import { delay, Observable, of } from "rxjs";
import type {
ApolloCache,
@@ -50,6 +51,7 @@ import type {
import {
actAsync,
createClientWrapper,
+ createMockWrapper,
markAsStreaming,
renderAsync,
renderHookAsync,
@@ -70,6 +72,8 @@ import type {
WatchQueryFetchPolicy,
} from "../../../core/watchQueryOptions.js";
+import { renderUseSuspenseQuery } from "./useSuspenseQuery/testUtils.js";
+
const IS_REACT_19 = React.version.startsWith("19");
type RenderSuspenseHookOptions = Omit<
@@ -96,6 +100,11 @@ interface SimpleQueryData {
greeting: string;
}
+/**
+ * @deprecated
+ * Use the `renderUseSuspenseQuery` helper from utils which uses render streams
+ * instead of function call render counting.
+ */
async function renderSuspenseHook(
render: (initialProps: Props) => Result,
options: RenderSuspenseHookOptions = {}
@@ -297,7 +306,10 @@ function useVariablesQueryCase() {
character: { __typename: "Character", id: String(index + 1), name },
},
},
- delay: 20,
+ // React runs layout effects much later in React 18 which means tracked
+ // components aren't captured correctly, specifically when changing
+ // variables that cause the component to suspend.
+ delay: IS_REACT_19 ? 20 : 200,
}));
return { query, mocks };
@@ -1085,47 +1097,51 @@ describe("useSuspenseQuery", () => {
it("suspends when changing variables", async () => {
const { query, mocks } = useVariablesQueryCase();
- const { result, rerenderAsync, renders } = await renderSuspenseHook(
+ using _disabledAct = disableActEnvironment();
+ const { rerender, takeRender } = await renderUseSuspenseQuery(
({ id }) => useSuspenseQuery(query, { variables: { id } }),
- { mocks, initialProps: { id: "1" } }
+ { wrapper: createMockWrapper({ mocks }), initialProps: { id: "1" } }
);
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[0].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- await rerenderAsync({ id: "2" });
+ await rerender({ id: "2" });
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[1].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- expect(renders.count).toBe(4 + (IS_REACT_19 ? renders.suspenseCount : 0));
- expect(renders.suspenseCount).toBe(2);
- expect(renders.frames).toStrictEqualTyped([
- {
- ...mocks[0].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
- ...mocks[1].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- ]);
+ await expect(takeRender).not.toRerender();
});
it("suspends and fetches data from new client when changing clients", async () => {
@@ -1564,135 +1580,137 @@ describe("useSuspenseQuery", () => {
link: new MockLink(mocks),
});
- const { result, rerenderAsync, renders } = await renderSuspenseHook(
+ using _disabledAct = disableActEnvironment();
+ const { rerender, takeRender } = await renderUseSuspenseQuery(
({ id }) => useSuspenseQuery(query, { variables: { id } }),
- { client, initialProps: { id: "1" } }
+ { wrapper: createClientWrapper(client), initialProps: { id: "1" } }
);
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[0].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- await rerenderAsync({ id: "2" });
+ await rerender({ id: "2" });
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[1].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- act(() => {
- client.writeQuery({
- query,
- variables: { id: "2" },
- data: { character: { id: "2", name: "Cached hero" } },
- });
+ client.writeQuery({
+ query,
+ variables: { id: "2" },
+ data: { character: { id: "2", name: "Cached hero" } },
});
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
data: { character: { id: "2", name: "Cached hero" } },
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- expect(renders.suspenseCount).toBe(2);
- expect(renders.count).toBe(5 + (IS_REACT_19 ? renders.suspenseCount : 0));
- expect(renders.frames).toStrictEqualTyped([
- {
- ...mocks[0].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
- ...mocks[1].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
- data: { character: { id: "2", name: "Cached hero" } },
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- ]);
+ await expect(takeRender).not.toRerender();
});
it("uses cached result and does not suspend when switching back to already used variables while using `cache-first` fetch policy", async () => {
const { query, mocks } = useVariablesQueryCase();
- const { result, rerenderAsync, renders } = await renderSuspenseHook(
+ using _disabledAct = disableActEnvironment();
+ const { rerender, takeRender } = await renderUseSuspenseQuery(
({ id }) =>
useSuspenseQuery(query, {
fetchPolicy: "cache-first",
variables: { id },
}),
- { mocks, initialProps: { id: "1" } }
+ { wrapper: createMockWrapper({ mocks }), initialProps: { id: "1" } }
);
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[0].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- await rerenderAsync({ id: "2" });
+ await rerender({ id: "2" });
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[1].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- await rerenderAsync({ id: "1" });
+ await rerender({ id: "1" });
- expect(result.current).toStrictEqualTyped({
- ...mocks[0].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- });
+ {
+ const { snapshot, renderedComponents } = await takeRender();
- expect(renders.count).toBe(5 + (IS_REACT_19 ? renders.suspenseCount : 0));
- expect(renders.suspenseCount).toBe(2);
- expect(renders.frames).toStrictEqualTyped([
- {
- ...mocks[0].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
- ...mocks[1].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[0].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
- },
- ]);
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
});
it("uses cached result with network request and does not suspend when switching back to already used variables while using `cache-and-network` fetch policy", async () => {
@@ -1714,7 +1732,7 @@ describe("useSuspenseQuery", () => {
character: { __typename: "Character", id: "1", name: "Spider-Man" },
},
},
- delay: 20,
+ delay: 200,
},
{
request: { query, variables: { id: "2" } },
@@ -1727,7 +1745,7 @@ describe("useSuspenseQuery", () => {
},
},
},
- delay: 20,
+ delay: 200,
},
{
request: { query, variables: { id: "1" } },
@@ -1740,85 +1758,85 @@ describe("useSuspenseQuery", () => {
},
},
},
- delay: 20,
+ delay: 200,
},
];
- const { result, rerenderAsync, renders } = await renderSuspenseHook(
+ using _disabledAct = disableActEnvironment();
+ const { rerender, takeRender } = await renderUseSuspenseQuery(
({ id }) =>
useSuspenseQuery(query, {
fetchPolicy: "cache-and-network",
variables: { id },
}),
- { mocks, initialProps: { id: "1" } }
+ { wrapper: createMockWrapper({ mocks }), initialProps: { id: "1" } }
);
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[0].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- await rerenderAsync({ id: "2" });
+ await rerender({ id: "2" });
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
- ...mocks[1].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- });
- });
+ {
+ const { renderedComponents } = await takeRender();
- await rerenderAsync({ id: "1" });
+ expect(renderedComponents).toStrictEqual([""]);
+ }
- expect(result.current).toStrictEqualTyped({
- ...mocks[0].result,
- dataState: "complete",
- networkStatus: NetworkStatus.loading,
- error: undefined,
- });
+ {
+ const { snapshot, renderedComponents } = await takeRender();
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
- ...mocks[2].result,
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ ...mocks[1].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- expect(renders.count).toBe(6 + (IS_REACT_19 ? renders.suspenseCount : 0));
- expect(renders.suspenseCount).toBe(2);
- expect(renders.frames).toStrictEqualTyped([
- {
- ...mocks[0].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
- ...mocks[1].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
+ await rerender({ id: "1" });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[0].result,
dataState: "complete",
networkStatus: NetworkStatus.loading,
error: undefined,
- },
- {
+ });
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[2].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
- },
- ]);
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
});
it("refetches and suspends when switching back to already used variables while using `network-only` fetch policy", async () => {
@@ -1840,7 +1858,7 @@ describe("useSuspenseQuery", () => {
character: { __typename: "Character", id: "1", name: "Spider-Man" },
},
},
- delay: 20,
+ delay: 200,
},
{
request: { query, variables: { id: "2" } },
@@ -1853,7 +1871,7 @@ describe("useSuspenseQuery", () => {
},
},
},
- delay: 20,
+ delay: 200,
},
{
request: { query, variables: { id: "1" } },
@@ -1866,72 +1884,79 @@ describe("useSuspenseQuery", () => {
},
},
},
- delay: 20,
+ delay: 200,
},
];
- const { result, rerenderAsync, renders } = await renderSuspenseHook(
+ using _disabledAct = disableActEnvironment();
+ const { rerender, takeRender } = await renderUseSuspenseQuery(
({ id }) =>
useSuspenseQuery(query, {
fetchPolicy: "network-only",
variables: { id },
}),
- { mocks, initialProps: { id: "1" } }
+ { wrapper: createMockWrapper({ mocks }), initialProps: { id: "1" } }
);
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[0].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- await rerenderAsync({ id: "2" });
+ await rerender({ id: "2" });
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[1].result,
- networkStatus: NetworkStatus.ready,
dataState: "complete",
+ networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- await rerenderAsync({ id: "1" });
+ await rerender({ id: "1" });
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[2].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- expect(renders.count).toBe(6 + (IS_REACT_19 ? renders.suspenseCount : 0));
- expect(renders.suspenseCount).toBe(3);
- expect(renders.frames).toStrictEqualTyped([
- {
- ...mocks[0].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
- ...mocks[1].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
- ...mocks[2].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- ]);
+ await expect(takeRender).not.toRerender();
});
it("refetches and suspends when switching back to already used variables while using `no-cache` fetch policy", async () => {
@@ -1953,7 +1978,7 @@ describe("useSuspenseQuery", () => {
character: { __typename: "Character", id: "1", name: "Spider-Man" },
},
},
- delay: 20,
+ delay: 200,
},
{
request: { query, variables: { id: "2" } },
@@ -1966,7 +1991,7 @@ describe("useSuspenseQuery", () => {
},
},
},
- delay: 20,
+ delay: 200,
},
{
request: { query, variables: { id: "1" } },
@@ -1979,72 +2004,79 @@ describe("useSuspenseQuery", () => {
},
},
},
- delay: 20,
+ delay: 200,
},
];
- const { result, rerenderAsync, renders } = await renderSuspenseHook(
+ using _disabledAct = disableActEnvironment();
+ const { rerender, takeRender } = await renderUseSuspenseQuery(
({ id }) =>
useSuspenseQuery(query, {
fetchPolicy: "no-cache",
variables: { id },
}),
- { mocks, initialProps: { id: "1" } }
+ { wrapper: createMockWrapper({ mocks }), initialProps: { id: "1" } }
);
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[0].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- await rerenderAsync({ id: "2" });
+ await rerender({ id: "2" });
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[1].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- await rerenderAsync({ id: "1" });
+ await rerender({ id: "1" });
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[2].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- expect(renders.count).toBe(6 + (IS_REACT_19 ? renders.suspenseCount : 0));
- expect(renders.suspenseCount).toBe(3);
- expect(renders.frames).toStrictEqualTyped([
- {
- ...mocks[0].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
- ...mocks[1].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
- ...mocks[2].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- ]);
+ await expect(takeRender).not.toRerender();
});
it("responds to cache updates after changing back to already fetched variables", async () => {
@@ -2055,87 +2087,83 @@ describe("useSuspenseQuery", () => {
link: new MockLink(mocks),
});
- const { result, rerenderAsync, renders } = await renderSuspenseHook(
+ using _disabledAct = disableActEnvironment();
+ const { rerender, takeRender } = await renderUseSuspenseQuery(
({ id }) => useSuspenseQuery(query, { variables: { id } }),
- { client, initialProps: { id: "1" } }
+ { wrapper: createClientWrapper(client), initialProps: { id: "1" } }
);
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[0].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- await rerenderAsync({ id: "2" });
+ await rerender({ id: "2" });
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[1].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- await rerenderAsync({ id: "1" });
+ await rerender({ id: "1" });
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[0].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- act(() => {
- client.writeQuery({
- query,
- variables: { id: "1" },
- data: { character: { id: "1", name: "Cached hero" } },
- });
+ client.writeQuery({
+ query,
+ variables: { id: "1" },
+ data: { character: { id: "1", name: "Cached hero" } },
});
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
data: { character: { id: "1", name: "Cached hero" } },
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- expect(renders.suspenseCount).toBe(2);
- expect(renders.count).toBe(6 + (IS_REACT_19 ? renders.suspenseCount : 0));
- expect(renders.frames).toStrictEqualTyped([
- {
- ...mocks[0].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
- ...mocks[1].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
- ...mocks[0].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
- data: { character: { id: "1", name: "Cached hero" } },
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- ]);
+ await expect(takeRender).not.toRerender();
});
it('does not suspend when data is in the cache and using a "cache-first" fetch policy', async () => {
@@ -2349,74 +2377,73 @@ describe("useSuspenseQuery", () => {
}
`;
- const cache = new InMemoryCache();
-
- cache.writeQuery({
- query: partialQuery,
- data: { character: { id: "1" } },
- variables: { id: "1" },
- });
-
- const { result, renders, rerenderAsync } = await renderSuspenseHook(
- ({ id }) =>
- useSuspenseQuery(fullQuery, {
- fetchPolicy: "cache-first",
- returnPartialData: true,
- variables: { id },
- }),
- { cache, mocks, initialProps: { id: "1" } }
- );
-
- expect(renders.suspenseCount).toBe(0);
- expect(result.current).toStrictEqualTyped({
- data: { character: { id: "1" } },
- dataState: "partial",
- networkStatus: NetworkStatus.loading,
- error: undefined,
- });
-
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
- ...mocks[0].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- });
- });
-
- await rerenderAsync({ id: "2" });
+ const cache = new InMemoryCache();
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
- ...mocks[1].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- });
+ cache.writeQuery({
+ query: partialQuery,
+ data: { character: { id: "1" } },
+ variables: { id: "1" },
});
- expect(renders.count).toBe(4 + (IS_REACT_19 ? renders.suspenseCount : 0));
- expect(renders.suspenseCount).toBe(1);
- expect(renders.frames).toStrictEqualTyped([
+ using _disabledAct = disableActEnvironment();
+ const { rerender, takeRender } = await renderUseSuspenseQuery(
+ ({ id }) =>
+ useSuspenseQuery(fullQuery, {
+ fetchPolicy: "cache-first",
+ returnPartialData: true,
+ variables: { id },
+ }),
{
+ wrapper: createMockWrapper({ cache, mocks }),
+ initialProps: { id: "1" },
+ }
+ );
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
data: { character: { id: "1" } },
dataState: "partial",
networkStatus: NetworkStatus.loading,
error: undefined,
- },
- {
+ });
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[0].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
- },
- {
+ });
+ }
+
+ await rerender({ id: "2" });
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[1].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
- },
- ]);
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
});
it('suspends when data is in the cache and using a "network-only" fetch policy', async () => {
@@ -2847,66 +2874,65 @@ describe("useSuspenseQuery", () => {
variables: { id: "1" },
});
- const { result, renders, rerenderAsync } = await renderSuspenseHook(
+ using _disabledAct = disableActEnvironment();
+ const { rerender, takeRender } = await renderUseSuspenseQuery(
({ id }) =>
useSuspenseQuery(fullQuery, {
fetchPolicy: "cache-and-network",
returnPartialData: true,
variables: { id },
}),
- { cache, mocks, initialProps: { id: "1" } }
+ {
+ wrapper: createMockWrapper({ cache, mocks }),
+ initialProps: { id: "1" },
+ }
);
- expect(renders.suspenseCount).toBe(0);
- expect(result.current).toStrictEqualTyped({
- data: { character: { id: "1" } },
- dataState: "partial",
- networkStatus: NetworkStatus.loading,
- error: undefined,
- });
+ {
+ const { snapshot, renderedComponents } = await takeRender();
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
- ...mocks[0].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: { character: { id: "1" } },
+ dataState: "partial",
+ networkStatus: NetworkStatus.loading,
error: undefined,
});
- });
+ }
- await rerenderAsync({ id: "2" });
+ {
+ const { snapshot, renderedComponents } = await takeRender();
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
- ...mocks[1].result,
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ ...mocks[0].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- expect(renders.count).toBe(4 + (IS_REACT_19 ? renders.suspenseCount : 0));
- expect(renders.suspenseCount).toBe(1);
- expect(renders.frames).toStrictEqualTyped([
- {
- data: { character: { id: "1" } },
- dataState: "partial",
- networkStatus: NetworkStatus.loading,
- error: undefined,
- },
- {
- ...mocks[0].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
+ await rerender({ id: "2" });
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[1].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
- },
- ]);
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
});
it.each([
@@ -3065,53 +3091,51 @@ describe("useSuspenseQuery", () => {
async (fetchPolicy) => {
const { query, mocks } = useVariablesQueryCase();
- const { result, rerenderAsync, renders } = await renderSuspenseHook(
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, rerender } = await renderUseSuspenseQuery(
({ id }) => useSuspenseQuery(query, { fetchPolicy, variables: { id } }),
- { mocks, initialProps: { id: "1" } }
+ { wrapper: createMockWrapper({ mocks }), initialProps: { id: "1" } }
);
- expect(renders.suspenseCount).toBe(1);
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[0].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- await rerenderAsync({ id: "2" });
+ await rerender({ id: "2" });
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
...mocks[1].result,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- // Renders:
- // 1. Initiate fetch and suspend
- // 2. Unsuspend and return results from initial fetch
- // 3. Change variables and suspend
- // 5. Unsuspend and return results from refetch
- expect(renders.count).toBe(4 + (IS_REACT_19 ? renders.suspenseCount : 0));
- expect(renders.suspenseCount).toBe(2);
- expect(renders.frames).toStrictEqualTyped([
- {
- ...mocks[0].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
- ...mocks[1].result,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- ]);
+ await expect(takeRender).not.toRerender();
}
);
@@ -3513,10 +3537,7 @@ describe("useSuspenseQuery", () => {
const client = new ApolloClient({
cache: new InMemoryCache(),
link: new ApolloLink((operation) => {
- return new Observable((observer) => {
- observer.next({ data: { vars: operation.variables } });
- observer.complete();
- });
+ return of({ data: { vars: operation.variables } }).pipe(delay(200));
}),
defaultOptions: {
watchQuery: {
@@ -3525,17 +3546,30 @@ describe("useSuspenseQuery", () => {
},
});
- const { result, rerenderAsync, renders } = await renderSuspenseHook(
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, rerender } = await renderUseSuspenseQuery(
({ source }) =>
useSuspenseQuery(query, {
fetchPolicy: "network-only",
variables: { source, localOnlyVar: true },
}),
- { client, initialProps: { source: "local" } }
+ {
+ wrapper: createClientWrapper(client),
+ initialProps: { source: "local" },
+ }
);
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
data: {
vars: { source: "local", globalOnlyVar: true, localOnlyVar: true },
},
@@ -3543,12 +3577,21 @@ describe("useSuspenseQuery", () => {
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- await rerenderAsync({ source: "rerender" });
+ await rerender({ source: "rerender" });
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
data: {
vars: { source: "rerender", globalOnlyVar: true, localOnlyVar: true },
},
@@ -3556,26 +3599,9 @@ describe("useSuspenseQuery", () => {
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- expect(renders.frames).toStrictEqualTyped([
- {
- data: {
- vars: { source: "local", globalOnlyVar: true, localOnlyVar: true },
- },
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- {
- data: {
- vars: { source: "rerender", globalOnlyVar: true, localOnlyVar: true },
- },
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- ]);
+ await expect(takeRender).not.toRerender();
});
it("can unset a globally defined variable", async () => {
@@ -4349,7 +4375,9 @@ describe("useSuspenseQuery", () => {
}
`;
- const graphQLErrors = [new GraphQLError("Could not fetch user 1")];
+ const graphQLErrors: GraphQLFormattedError[] = [
+ { message: "Could not fetch user 1" },
+ ];
const mocks = [
{
@@ -4357,63 +4385,65 @@ describe("useSuspenseQuery", () => {
result: {
errors: graphQLErrors,
},
- delay: 20,
+ delay: 200,
},
{
request: { query, variables: { id: "2" } },
result: {
data: { user: { id: "2", name: "Captain Marvel" } },
},
- delay: 20,
+ delay: 200,
},
];
- const { result, renders, rerenderAsync } = await renderSuspenseHook(
+ using _disabledAct = disableActEnvironment();
+ const { rerender, takeRender } = await renderUseSuspenseQuery(
({ id }) =>
useSuspenseQuery(query, { errorPolicy: "all", variables: { id } }),
- { mocks, initialProps: { id: "1" } }
+ { wrapper: createMockWrapper({ mocks }), initialProps: { id: "1" } }
);
const expectedError = new CombinedGraphQLErrors({ errors: graphQLErrors });
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
data: undefined,
dataState: "empty",
networkStatus: NetworkStatus.error,
error: expectedError,
});
- });
+ }
- await rerenderAsync({ id: "2" });
+ await rerender({ id: "2" });
- await waitFor(() => {
- expect(result.current).toStrictEqualTyped({
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
data: mocks[1].result.data,
dataState: "complete",
networkStatus: NetworkStatus.ready,
error: undefined,
});
- });
+ }
- expect(renders.count).toBe(4 + (IS_REACT_19 ? renders.suspenseCount : 0));
- expect(renders.errorCount).toBe(0);
- expect(renders.errors).toEqual([]);
- expect(renders.suspenseCount).toBe(2);
- expect(renders.frames).toStrictEqualTyped([
- {
- data: undefined,
- dataState: "empty",
- networkStatus: NetworkStatus.error,
- error: expectedError,
- },
- {
- data: mocks[1].result.data,
- dataState: "complete",
- networkStatus: NetworkStatus.ready,
- error: undefined,
- },
- ]);
+ await expect(takeRender).not.toRerender();
});
it("re-suspends when calling `refetch`", async () => {
diff --git a/src/react/hooks/__tests__/useSuspenseQuery/skipToken.test.tsx b/src/react/hooks/__tests__/useSuspenseQuery/skipToken.test.tsx
new file mode 100644
index 00000000000..698f402312f
--- /dev/null
+++ b/src/react/hooks/__tests__/useSuspenseQuery/skipToken.test.tsx
@@ -0,0 +1,269 @@
+import { disableActEnvironment } from "@testing-library/react-render-stream";
+import { delay, of } from "rxjs";
+
+import {
+ ApolloClient,
+ ApolloLink,
+ InMemoryCache,
+ NetworkStatus,
+} from "@apollo/client";
+import { skipToken, useSuspenseQuery } from "@apollo/client/react";
+import { MockLink } from "@apollo/client/testing";
+import {
+ createClientWrapper,
+ createMockWrapper,
+ setupVariablesCase,
+} from "@apollo/client/testing/internal";
+
+import { renderUseSuspenseQuery } from "./testUtils.js";
+
+// https://github.com/apollographql/apollo-client/issues/12989
+test("maintains variables when switching to `skipToken` and calling `refetchQueries` while skipped after initial request", async () => {
+ const { query } = setupVariablesCase();
+
+ const client = new ApolloClient({
+ link: new ApolloLink((operation) => {
+ return of(
+ operation.variables.id === "1" ?
+ {
+ data: {
+ character: {
+ __typename: "Character",
+ id: "1",
+ name: "Spider-Man",
+ },
+ },
+ }
+ : {
+ data: null,
+ errors: [
+ { message: `Fetched wrong id: ${operation.variables.id}` },
+ ],
+ }
+ ).pipe(delay(10));
+ }),
+ cache: new InMemoryCache(),
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, rerender } = await renderUseSuspenseQuery(
+ ({ id }) =>
+ useSuspenseQuery(
+ query,
+ id === undefined ? skipToken : { variables: { id } }
+ ),
+ {
+ initialProps: { id: "1" as string | undefined },
+ wrapper: createClientWrapper(client),
+ }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot } = await takeRender();
+
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "1", name: "Spider-Man" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await rerender({ id: undefined });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "1", name: "Spider-Man" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+
+ await expect(
+ client.refetchQueries({ include: [query] })
+ ).resolves.toStrictEqualTyped([
+ {
+ data: {
+ character: { __typename: "Character", id: "1", name: "Spider-Man" },
+ },
+ },
+ ]);
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("suspends and fetches when changing variables when no longer using skipToken", async () => {
+ const { query, mocks } = setupVariablesCase({ delay: 200 });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, rerender } = await renderUseSuspenseQuery(
+ ({ id }) =>
+ useSuspenseQuery(
+ query,
+ id === undefined ? skipToken : { variables: { id } }
+ ),
+ {
+ initialProps: { id: "1" as string | undefined },
+ wrapper: createMockWrapper({ mocks }),
+ }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot } = await takeRender();
+
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "1", name: "Spider-Man" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await rerender({ id: undefined });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "1", name: "Spider-Man" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+
+ await rerender({ id: "2" });
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot } = await takeRender();
+
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "2", name: "Black Widow" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
+
+test("does not suspend for data in the cache when changing variables when no longer using skipToken", async () => {
+ const { query, mocks } = setupVariablesCase();
+
+ const client = new ApolloClient({
+ link: new MockLink(mocks),
+ cache: new InMemoryCache(),
+ });
+
+ client.writeQuery({
+ query,
+ data: {
+ character: { __typename: "Character", id: "2", name: "Cached Widow" },
+ },
+ variables: { id: "2" },
+ });
+
+ using _disabledAct = disableActEnvironment();
+ const { takeRender, rerender } = await renderUseSuspenseQuery(
+ ({ id }) =>
+ useSuspenseQuery(
+ query,
+ id === undefined ? skipToken : { variables: { id } }
+ ),
+ {
+ initialProps: { id: "1" as string | undefined },
+ wrapper: createClientWrapper(client),
+ }
+ );
+
+ {
+ const { renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual([""]);
+ }
+
+ {
+ const { snapshot } = await takeRender();
+
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "1", name: "Spider-Man" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await rerender({ id: undefined });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "1", name: "Spider-Man" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await rerender({ id: "2" });
+
+ {
+ const { snapshot, renderedComponents } = await takeRender();
+
+ expect(renderedComponents).toStrictEqual(["useSuspenseQuery"]);
+ expect(snapshot).toStrictEqualTyped({
+ data: {
+ character: { __typename: "Character", id: "2", name: "Cached Widow" },
+ },
+ dataState: "complete",
+ error: undefined,
+ networkStatus: NetworkStatus.ready,
+ });
+ }
+
+ await expect(takeRender).not.toRerender();
+});
diff --git a/src/react/hooks/__tests__/useSuspenseQuery/testUtils.tsx b/src/react/hooks/__tests__/useSuspenseQuery/testUtils.tsx
new file mode 100644
index 00000000000..8554c0f566e
--- /dev/null
+++ b/src/react/hooks/__tests__/useSuspenseQuery/testUtils.tsx
@@ -0,0 +1,73 @@
+import type { RenderOptions } from "@testing-library/react";
+import {
+ createRenderStream,
+ useTrackRenders,
+} from "@testing-library/react-render-stream";
+import React, { Suspense } from "react";
+import { ErrorBoundary } from "react-error-boundary";
+
+import type { ErrorLike, OperationVariables } from "@apollo/client";
+import type { useSuspenseQuery } from "@apollo/client/react";
+import { invariant } from "@apollo/client/utilities/invariant";
+
+export async function renderUseSuspenseQuery<
+ TData,
+ TVariables extends OperationVariables,
+ Props = never,
+>(
+ renderHook: (props: any) => any,
+ options: Pick & { initialProps?: Props }
+) {
+ function UseSuspenseQuery({ props }: { props: Props | undefined }) {
+ useTrackRenders({ name: "useSuspenseQuery" });
+ replaceSnapshot(renderHook(props as any));
+
+ return null;
+ }
+
+ function SuspenseFallback() {
+ useTrackRenders({ name: "" });
+
+ return null;
+ }
+
+ function ErrorFallback() {
+ useTrackRenders({ name: "" });
+
+ return null;
+ }
+
+ function App({ props }: { props: Props | undefined }) {
+ return (
+ }>
+ replaceSnapshot({ error })}
+ >
+
+
+
+ );
+ }
+
+ const { render, takeRender, replaceSnapshot, getCurrentRender } =
+ createRenderStream<
+ useSuspenseQuery.Result | { error: ErrorLike }
+ >({ skipNonTrackingRenders: true });
+
+ const utils = await render(, options);
+
+ function rerender(props: Props) {
+ return utils.rerender();
+ }
+
+ function getCurrentSnapshot() {
+ const { snapshot } = getCurrentRender();
+
+ invariant("data" in snapshot, "Snapshot is not a hook snapshot");
+
+ return snapshot;
+ }
+
+ return { getCurrentSnapshot, rerender, takeRender };
+}
diff --git a/src/react/hooks/internal/index.ts b/src/react/hooks/internal/index.ts
index f930bd3998c..301a51269a6 100644
--- a/src/react/hooks/internal/index.ts
+++ b/src/react/hooks/internal/index.ts
@@ -1,5 +1,6 @@
// These hooks are used internally and are not exported publicly by the library
export { useDeepMemo } from "./useDeepMemo.js";
export { useRenderGuard } from "./useRenderGuard.js";
+export { useSuspenseHookCacheKey } from "./useSuspenseHookCacheKey.js";
export { __use } from "./__use.js";
export { wrapHook } from "./wrapHook.js";
diff --git a/src/react/hooks/internal/useSuspenseHookCacheKey.ts b/src/react/hooks/internal/useSuspenseHookCacheKey.ts
new file mode 100644
index 00000000000..f3c58fa9694
--- /dev/null
+++ b/src/react/hooks/internal/useSuspenseHookCacheKey.ts
@@ -0,0 +1,46 @@
+import type { DocumentNode } from "graphql";
+import * as React from "react";
+
+import type { OperationVariables } from "@apollo/client";
+import type { CacheKey } from "@apollo/client/react/internal";
+import { canonicalStringify } from "@apollo/client/utilities";
+
+import type { SkipToken } from "../constants.js";
+import { skipToken } from "../constants.js";
+
+export declare namespace useSuspenseHookCacheKey {
+ export interface Options {
+ variables?: OperationVariables;
+ queryKey?: string | number | any[];
+ }
+}
+
+export function useSuspenseHookCacheKey(
+ query: DocumentNode,
+ options:
+ | (SkipToken & Partial)
+ | useSuspenseHookCacheKey.Options
+) {
+ const { queryKey = [], variables } = options;
+ const canonicalVariables = canonicalStringify(variables);
+
+ // This state value let's us maintain the variables used for the cache key
+ // when `skipToken` is used to skip a query after its been executed.
+ // Since options aren't provided when using `skipToken`, `variables` would
+ // otherwise disappear which means we'd return a new cache key without a
+ // variables value which creates a new `ObservableQuery` instance. This was
+ // particularly problematic when `refetchQueries` was used because it meant
+ // refetching against an `ObservableQuery` instance that had no variables.
+ let [cacheKeyVariables, setCacheKeyVariables] =
+ React.useState(canonicalVariables);
+
+ if (options !== skipToken && cacheKeyVariables !== canonicalVariables) {
+ setCacheKeyVariables((cacheKeyVariables = canonicalVariables));
+ }
+
+ return [
+ query,
+ cacheKeyVariables,
+ ...([] as any[]).concat(queryKey),
+ ] satisfies CacheKey;
+}
diff --git a/src/react/hooks/useBackgroundQuery.ts b/src/react/hooks/useBackgroundQuery.ts
index 6634b99adc4..88f98543d87 100644
--- a/src/react/hooks/useBackgroundQuery.ts
+++ b/src/react/hooks/useBackgroundQuery.ts
@@ -12,10 +12,8 @@ import type {
WatchQueryFetchPolicy,
} from "@apollo/client";
import type { SubscribeToMoreFunction } from "@apollo/client";
-import { canonicalStringify } from "@apollo/client/cache";
import type { QueryRef } from "@apollo/client/react";
import type {
- CacheKey,
FetchMoreFunction,
RefetchFunction,
} from "@apollo/client/react/internal";
@@ -32,7 +30,7 @@ import type {
} from "@apollo/client/utilities/internal";
import type { SkipToken } from "./constants.js";
-import { wrapHook } from "./internal/index.js";
+import { useSuspenseHookCacheKey, wrapHook } from "./internal/index.js";
import { useApolloClient } from "./useApolloClient.js";
import { useWatchQueryOptions } from "./useSuspenseQuery.js";
@@ -449,8 +447,8 @@ function useBackgroundQuery_<
const client = useApolloClient(options.client);
const suspenseCache = getSuspenseCache(client);
const watchQueryOptions = useWatchQueryOptions({ client, query, options });
- const { fetchPolicy, variables } = watchQueryOptions;
- const { queryKey = [] } = options;
+ const { fetchPolicy } = watchQueryOptions;
+ const cacheKey = useSuspenseHookCacheKey(query, options);
// This ref tracks the first time query execution is enabled to determine
// whether to return a query ref or `undefined`. When initialized
@@ -461,12 +459,6 @@ function useBackgroundQuery_<
const didFetchResult = React.useRef(fetchPolicy !== "standby");
didFetchResult.current ||= fetchPolicy !== "standby";
- const cacheKey: CacheKey = [
- query,
- canonicalStringify(variables),
- ...([] as any[]).concat(queryKey),
- ];
-
const queryRef = suspenseCache.getQueryRef(cacheKey, () =>
client.watchQuery(
watchQueryOptions as ApolloClient.WatchQueryOptions
diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts
index b8ff4434a72..4e41f801859 100644
--- a/src/react/hooks/useSuspenseQuery.ts
+++ b/src/react/hooks/useSuspenseQuery.ts
@@ -17,9 +17,7 @@ import type {
} from "@apollo/client";
import type { SubscribeToMoreFunction } from "@apollo/client";
import { NetworkStatus } from "@apollo/client";
-import { canonicalStringify } from "@apollo/client/cache";
import type {
- CacheKey,
FetchMoreFunction,
QueryKey,
RefetchFunction,
@@ -34,7 +32,12 @@ import type {
import type { SkipToken } from "./constants.js";
import { skipToken } from "./constants.js";
-import { __use, useDeepMemo, wrapHook } from "./internal/index.js";
+import {
+ __use,
+ useDeepMemo,
+ useSuspenseHookCacheKey,
+ wrapHook,
+} from "./internal/index.js";
import { validateSuspenseHookOptions } from "./internal/validateSuspenseHookOptions.js";
import { useApolloClient } from "./useApolloClient.js";
@@ -375,14 +378,8 @@ function useSuspenseQuery_<
query,
options,
});
- const { fetchPolicy, variables } = watchQueryOptions;
- const { queryKey = [] } = options;
-
- const cacheKey: CacheKey = [
- query,
- canonicalStringify(variables),
- ...([] as any[]).concat(queryKey),
- ];
+ const { fetchPolicy } = watchQueryOptions;
+ const cacheKey = useSuspenseHookCacheKey(query, options);
const queryRef = suspenseCache.getQueryRef(cacheKey, () =>
client.watchQuery(watchQueryOptions)
diff --git a/src/testing/internal/scenarios/index.ts b/src/testing/internal/scenarios/index.ts
index 8b0d48ee69f..32f86aaddef 100644
--- a/src/testing/internal/scenarios/index.ts
+++ b/src/testing/internal/scenarios/index.ts
@@ -38,7 +38,9 @@ export interface VariablesCaseVariables {
id: string;
}
-export function setupVariablesCase() {
+export function setupVariablesCase({
+ delay = 20,
+}: { delay?: MockLink.Delay } = {}) {
const query: TypedDocumentNode =
gql`
query CharacterQuery($id: ID!) {
@@ -60,7 +62,7 @@ export function setupVariablesCase() {
character: { __typename: "Character", id: String(index + 1), name },
},
},
- delay: 20,
+ delay,
}));
return { mocks, query };