1import * as React from 'react';
2import { ServerInsertedHTMLContext } from 'next/navigation';
3import type { UrqlResult } from './useUrqlValue';
4import { htmlEscapeJsonString } from './htmlescape';
5
6interface DataHydrationValue {
7 isInjecting: boolean;
8 operationValuesByKey: Record<number, UrqlResult>;
9 RehydrateScript: () =>
10 | React.DetailedReactHTMLElement<
11 { dangerouslySetInnerHTML: { __html: string } },
12 HTMLElement
13 >
14 | React.FunctionComponentElement<any>;
15}
16
17const DataHydrationContext = React.createContext<
18 DataHydrationValue | undefined
19>(undefined);
20
21function transportDataToJS(data: any) {
22 const key = 'urql_transport';
23 return `(window[Symbol.for("${key}")] ??= []).push(${htmlEscapeJsonString(
24 JSON.stringify(data)
25 )})`;
26}
27
28export const DataHydrationContextProvider = ({
29 nonce,
30 children,
31}: React.PropsWithChildren<{ nonce?: string }>) => {
32 const dataHydrationContext = React.useRef<DataHydrationValue>();
33 if (typeof window == 'undefined') {
34 if (!dataHydrationContext.current)
35 dataHydrationContext.current = buildContext({ nonce });
36 }
37
38 return React.createElement(
39 DataHydrationContext.Provider,
40 { value: dataHydrationContext.current },
41 children
42 );
43};
44
45export function useDataHydrationContext(): DataHydrationValue | undefined {
46 const dataHydrationContext = React.useContext(DataHydrationContext);
47 const insertHtml = React.useContext(ServerInsertedHTMLContext as any) as (
48 cb: () => any
49 ) => any;
50
51 if (typeof window !== 'undefined') return;
52
53 if (insertHtml && dataHydrationContext && !dataHydrationContext.isInjecting) {
54 dataHydrationContext.isInjecting = true;
55 insertHtml(() =>
56 React.createElement(dataHydrationContext.RehydrateScript, {})
57 );
58 }
59 return dataHydrationContext;
60}
61
62let key = 0;
63function buildContext({ nonce }: { nonce?: string }): DataHydrationValue {
64 const dataHydrationContext: DataHydrationValue = {
65 isInjecting: false,
66 operationValuesByKey: {},
67 RehydrateScript() {
68 dataHydrationContext.isInjecting = false;
69 if (!Object.keys(dataHydrationContext.operationValuesByKey).length)
70 return React.createElement(React.Fragment);
71
72 const __html = transportDataToJS({
73 rehydrate: { ...dataHydrationContext.operationValuesByKey },
74 });
75
76 dataHydrationContext.operationValuesByKey = {};
77
78 return React.createElement('script', {
79 key: key++,
80 nonce: nonce,
81 dangerouslySetInnerHTML: { __html },
82 });
83 },
84 };
85
86 return dataHydrationContext;
87}