UNPKG

@apollo/client-react-streaming

Version:

This package provides building blocks to create framework-level integration of Apollo Client with React's streaming SSR. See the [@apollo/client-integration-nextjs](https://github.com/apollographql/apollo-client-integrations/tree/main/packages/nextjs) pac

137 lines (131 loc) 6.94 kB
import React from 'react'; import { DataTransportProviderImplementation } from '@apollo/client-react-streaming'; /** * @public */ interface HydrationContextOptions { /** * Props that will be passed down to `script` tags that will be used to transport * data to the browser. * Can e.g. be used to add a `nonce`. */ extraScriptProps?: ScriptProps; } type SerializableProps<T> = Pick<T, { [K in keyof T]: T[K] extends string | number | boolean | undefined | null ? K : never; }[keyof T]>; type ScriptProps = SerializableProps<React.ScriptHTMLAttributes<HTMLScriptElement>>; interface ManualDataTransportOptions { /** * A hook that allows for insertion into the stream. * Will only be called during SSR, doesn't need to actiually return something otherwise. */ useInsertHtml(): (callbacks: () => React.ReactNode) => void; /** * Prepare data for injecting into the stream by converting it into a string that can be parsed as JavaScript by the browser. * Could e.g. be `SuperJSON.stringify` or `serialize-javascript`. * The default implementation act like a JSON.stringify that preserves `undefined`, but not do much on top of that. */ stringifyForStream?: (value: any) => string; /** * If necessary, additional deserialization steps that need to be applied on top of executing the result of `stringifyForStream` in the browser. * Could e.g. be `SuperJSON.deserialize`. (Not needed in the case of using `serialize-javascript`) */ reviveFromStream?: (value: any) => any; /** * **Read the whole comment before using this option!** * * If `true`, the `useStaticValueRef` hook will not transport values over to the client. * This hook is used to transport the values of hook calls during SSR to the client, to ensure that * the client will rehydrate with the exact same values as it rendered on the server. * * This mechanism is in place to prevent hydration mismatches as described in * https://github.com/apollographql/apollo-client-integrations/blob/pr/RFC-2/RFC.md#challenges-of-a-client-side-cache-in-streaming-ssr * (first graph of the "Challenges of a client-side cache in streaming SSR" section). * * Setting this value to `true` will save on data transported over the wire, but comes with the risk * of hydration mismatches. * Strongly discouraged with older React versions, as hydration mismatches there will likely crash * the application, setting this to `true` might be okay with React 19, which is much better at recovering * from hydration mismatches - but it still comes with a risk. * When enabling this, you should closely monitor error reporting and user feedback. */ dangerous_disableHookValueTransportation?: boolean; } /** * > This export is only available in React Client Components * * Creates a "manual" Data Transport, to be used with `WrapApolloProvider`. * * @remarks * * ### Drawbacks * * While this Data Transport enables streaming SSR, it has some conceptual drawbacks: * * - It does not have a way of keeping your connection open if your application already finished, but there are still ongoing queries that might need to be transported over. * - This can happen if a component renders `useBackgroundQuery`, but does not read the `queryRef` with `useReadQuery` * - These "cut off" queries will be restarted in the browser once the browser's `load` event happens * - If the `useInsertHtml` doesn't immediately flush data to the browser, the browser might already attain "newer" data through queries triggered by user interaction. * - This delayed behaviour is the case with the Next.js `ServerInsertedHTMLContext` and in the example Vite implementation. * - In this, case, older data from the server might overwrite newer data in the browser. This is minimized by simulating ongoing queries in the browser once the information of a started query is transported over. * If the browser would try to trigger the exact same query, query deduplication would make the browser wait for the server query to resolve instead. * - For more timing-related details, see https://github.com/apollographql/apollo-client-integrations/pull/9 * * To fully work around these drawbacks, React needs to add "data injection into the stream" to it's public API, which is not the case today. * We provide an [example with a patched React version](https://github.com/apollographql/apollo-client-integrations/blob/main/integration-test/experimental-react) to showcase how that could look. * * @example * For usage examples, see the implementation of the `@apollo/client-integration-nextjs` * [`ApolloNextAppProvider`](https://github.com/apollographql/apollo-client-integrations/blob/c0715a05cf8ca29a3cbb9ce294cdcbc5ce251b2e/packages/experimental-nextjs-app-support/src/ApolloNextAppProvider.ts) * * ```tsx * export const ApolloNextAppProvider = WrapApolloProvider( * buildManualDataTransport({ * useInsertHtml() { * const insertHtml = useContext(ServerInsertedHTMLContext); * if (!insertHtml) { * throw new Error( * "ApolloNextAppProvider cannot be used outside of the Next App Router!" * ); * } * return insertHtml; * }, * }) * ); * ``` * * @example * Another usage example is our integration example with Vite and streaming SSR, which you can find at https://github.com/apollographql/apollo-client-integrations/tree/main/integration-test/vite-streaming * * @public */ declare const buildManualDataTransport: (args: ManualDataTransportOptions) => DataTransportProviderImplementation<HydrationContextOptions>; type ValidQueueKeys = { [K in keyof Window]-?: NonNullable<Window[K]> extends { push(...args: any[]): any; } ? K : never; }[keyof Window]; /** * Registers a queue that can be filled with data before it has actually been initialized with this function. * Before calling this function, `window[key]` can just be handled as an array of data. * When calling this funcation, all accumulated data will be passed to the callback. * After calling this function, `window[key]` will be an object with a `push` method that will call the callback with the data. * * @public */ declare function registerLateInitializingQueue<K extends ValidQueueKeys>(key: K, callback: (data: Parameters<NonNullable<Window[K]>["push"]>[0]) => void): void; /** * > This export is only available in React Client Components * * Resets the singleton instances created for the Apollo SSR data transport and caches. * * To be used in testing only, like * ```ts * afterEach(resetManualSSRApolloSingletons); * ``` * * @public */ declare function resetManualSSRApolloSingletons(): void; export { type HydrationContextOptions, buildManualDataTransport, registerLateInitializingQueue, resetManualSSRApolloSingletons };