Skip to content

Commit

Permalink
feat(package/react): streaming SSR on react-dom@>=18 (#2049)
Browse files Browse the repository at this point in the history
* feat(package/react): SSR uses renderToPipeableStream on react-dom@>=18

* feat: support web standard runtimes

* chore(package/react): add peers for React 19
  • Loading branch information
vicary authored Jan 22, 2025
1 parent 46b8a4d commit 2367645
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/fluffy-kings-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@gqty/react': minor
---

streaming SSR on react-dom@>=18
4 changes: 3 additions & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
"jest-environment-jsdom": "^29.7.0",
"lodash-es": "^4.17.21",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-test-renderer": "^18.3.1",
"test-utils": "workspace:^",
"type-fest": "^4.24.0",
Expand All @@ -124,7 +125,8 @@
"graphql": "*",
"graphql-sse": "^2.5.3",
"graphql-ws": "^5.16.0",
"react": "^16.14.0 || ^17 || ^18"
"react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"graphql-sse": {
Expand Down
36 changes: 32 additions & 4 deletions packages/react/src/ssr/ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import {
type GQtyClient,
type LegacyHydrateCacheOptions,
} from 'gqty';
import { useEffect, useMemo, type ReactNode } from 'react';
import { type ReactNode, useEffect, useMemo } from 'react';
import { version } from 'react-dom/server';
import { getDefault, type ReactClientOptionsWithDefaults } from '../utils';

export interface UseHydrateCacheOptions
Expand All @@ -25,7 +26,7 @@ export interface UseHydrateCacheOptions
* Props with `cacheSnapshot` that would be returned from `prepareReactRender`
*/
export type PropsWithServerCache<
T extends Record<string | number, unknown> = Record<string | number, unknown>
T extends Record<string | number, unknown> = Record<string | number, unknown>,
> = {
/**
* Cache snapshot, returned from `prepareReactRender`
Expand All @@ -51,10 +52,37 @@ export function createSSRHelpers<TSchema extends BaseGeneratedSchema>(
) {
const prepareReactRender: PrepareReactRender =
async function prepareReactRender(element: ReactNode) {
const ssrPrepass = getDefault(await import('react-ssr-prepass'));
const majorVersion = +version.split('.')[0];

return prepareRender(() => ssrPrepass(element));
if (majorVersion >= 18) {
const { renderToPipeableStream, renderToReadableStream } = await import(
'react-dom/server'
);

if (renderToReadableStream !== undefined) {
return prepareRender(async () => {
const stream = await renderToReadableStream(element);

await stream.allReady;
});
} else {
return prepareRender(
() =>
new Promise((resolve, reject) => {
renderToPipeableStream(element, {
onAllReady: resolve,
onError: reject,
});
})
);
}
} else {
const ssrPrepass = getDefault(await import('react-ssr-prepass'));

return prepareRender(() => ssrPrepass(element));
}
};

const useHydrateCache: UseHydrateCache = function useHydrateCache({
cacheSnapshot,
shouldRefetch = refetchAfterHydrate,
Expand Down
10 changes: 5 additions & 5 deletions packages/react/src/useThrottledAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,31 +65,31 @@ export function useThrottledAsync<Result, Args extends unknown[] = unknown[]>(
): [
AsyncState<Result>,
UseAsyncActions<Result, Args>,
UseAsyncMeta<Result, Args>
UseAsyncMeta<Result, Args>,
];
export function useThrottledAsync<Result, Args extends unknown[] = unknown[]>(
asyncFn: (...params: Args) => Promise<Result>,
initialValue?: Result
): [
AsyncState<Result | undefined>,
UseAsyncActions<Result, Args>,
UseAsyncMeta<Result, Args>
UseAsyncMeta<Result, Args>,
];
export function useThrottledAsync<Result, Args extends unknown[] = unknown[]>(
asyncFn: (...params: Args) => Promise<Result>,
initialValue?: Result
): [
AsyncState<Result | undefined>,
UseAsyncActions<Result, Args>,
UseAsyncMeta<Result, Args>
UseAsyncMeta<Result, Args>,
] {
const [state, setState] = useState<AsyncState<Result | undefined>>({
status: 'not-executed',
error: undefined,
result: initialValue,
});
const promiseRef = useRef<Promise<Result>>();
const argsRef = useRef<Args>();
const promiseRef = useRef<Promise<Result> | undefined>(undefined);
const argsRef = useRef<Args | undefined>(undefined);

const methods = useSyncedRef({
execute: (...params: Args) => {
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 2367645

Please sign in to comment.