Skip to content

Commit b1a2f84

Browse files
committed
allow enabling "forced suspense" for useQuery hook in SSR
1 parent 1f8cda5 commit b1a2f84

File tree

6 files changed

+89
-2
lines changed

6 files changed

+89
-2
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"use client";
2+
3+
import { useQuery } from "@apollo/experimental-nextjs-app-support/ssr";
4+
import { enableSSRWaitForUseQuery } from "@apollo/client-react-streaming";
5+
import type { TypedDocumentNode } from "@apollo/client";
6+
import { gql, useApolloClient } from "@apollo/client";
7+
8+
const QUERY: TypedDocumentNode<{
9+
products: {
10+
id: string;
11+
title: string;
12+
}[];
13+
}> = gql`
14+
query dynamicProducts {
15+
products {
16+
id
17+
title
18+
}
19+
}
20+
`;
21+
22+
export const dynamic = "force-dynamic";
23+
24+
export default function Page() {
25+
enableSSRWaitForUseQuery(useApolloClient());
26+
const result = useQuery(QUERY);
27+
globalThis.hydrationFinished?.();
28+
29+
if (!result.data) {
30+
return <div>Loading...</div>;
31+
}
32+
33+
return (
34+
<ul>
35+
{result.data.products.map(({ id, title }) => (
36+
<li key={id}>{title}</li>
37+
))}
38+
</ul>
39+
);
40+
}

packages/client-react-streaming/package-shape.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"SSRMultipartLink",
1919
"WrapApolloProvider",
2020
"resetApolloSingletons",
21+
"enableSSRWaitForUseQuery",
2122
"built_for_browser"
2223
],
2324
"node": [
@@ -29,6 +30,7 @@
2930
"SSRMultipartLink",
3031
"WrapApolloProvider",
3132
"resetApolloSingletons",
33+
"enableSSRWaitForUseQuery",
3234
"built_for_ssr"
3335
],
3436
"edge-light,worker,browser": [
@@ -40,6 +42,7 @@
4042
"SSRMultipartLink",
4143
"WrapApolloProvider",
4244
"resetApolloSingletons",
45+
"enableSSRWaitForUseQuery",
4346
"built_for_ssr"
4447
]
4548
},

packages/client-react-streaming/src/DataTransportAbstraction/WrappedApolloClient.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import type {
2525
} from "./DataTransportAbstraction.js";
2626
import { bundle } from "../bundleInfo.js";
2727

28-
function getQueryManager<TCacheShape>(
28+
export function getQueryManager<TCacheShape>(
2929
client: OrigApolloClient<unknown>
3030
): QueryManager<TCacheShape> & {
3131
[wrappers]: HookWrappers;
@@ -39,7 +39,7 @@ type SimulatedQueryInfo = {
3939
options: WatchQueryOptions<OperationVariables, any>;
4040
};
4141

42-
const wrappers = Symbol.for("apollo.hook.wrappers");
42+
export const wrappers = Symbol.for("apollo.hook.wrappers");
4343
class ApolloClientBase<TCacheShape> extends OrigApolloClient<TCacheShape> {
4444
/**
4545
* Information about the current package and it's export names, for use in error messages.

packages/client-react-streaming/src/DataTransportAbstraction/hooks.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import type { HookWrappers } from "@apollo/client/react/internal/index.js";
22
import { useTransportValue } from "./useTransportValue.js";
3+
import type { WatchQueryOptions } from "@apollo/client/index.js";
4+
import { useApolloClient } from "@apollo/client/index.js";
5+
import { getSuspenseCache } from "@apollo/client/react/internal/index.js";
6+
import { canonicalStringify } from "@apollo/client/cache/index.js";
7+
import { use } from "react";
8+
import type { ApolloClient } from "./WrappedApolloClient.js";
9+
import { getQueryManager, wrappers } from "./WrappedApolloClient.js";
310

411
export const hookWrappers: HookWrappers = {
512
useFragment(orig_useFragment) {
@@ -35,3 +42,38 @@ function wrap<T extends (...args: any[]) => any>(
3542
return { ...result, ...useTransportValue(transported) };
3643
}) as T;
3744
}
45+
46+
export const enableSSRWaitForUseQuery: (client: ApolloClient<any>) => void =
47+
process.env.REACT_ENV === "ssr"
48+
? (client) => {
49+
getQueryManager(client)[wrappers].useQuery = (orig_useQuery) =>
50+
wrap<typeof orig_useQuery>(
51+
function useQuery(query, options) {
52+
const client = useApolloClient();
53+
const result = client.cache.read({
54+
query,
55+
variables: options?.variables,
56+
returnPartialData: options?.returnPartialData,
57+
optimistic: false,
58+
});
59+
if (!result) {
60+
const queryRef = getSuspenseCache(client).getQueryRef(
61+
[query, canonicalStringify(options?.variables), "useQuery"],
62+
() =>
63+
client.watchQuery({
64+
query,
65+
...(options as Partial<WatchQueryOptions>),
66+
})
67+
);
68+
use(queryRef.promise);
69+
}
70+
71+
return orig_useQuery(query, {
72+
...options,
73+
fetchPolicy: "cache-only",
74+
});
75+
},
76+
["data", "loading", "networkStatus", "called"]
77+
);
78+
}
79+
: () => {};

packages/client-react-streaming/src/DataTransportAbstraction/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export {
1212
WrapApolloProvider,
1313
WrappedApolloProvider,
1414
} from "./WrapApolloProvider.js";
15+
export { enableSSRWaitForUseQuery } from "./hooks.js";

packages/client-react-streaming/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export {
66
QueryEvent,
77
WrapApolloProvider,
88
WrappedApolloProvider,
9+
enableSSRWaitForUseQuery,
910
} from "./DataTransportAbstraction/index.js";

0 commit comments

Comments
 (0)