UNPKG

@suspensive/react-query-4

Version:

Suspensive interfaces for @tanstack/react-query@4

1 lines 5.95 kB
{"version":3,"file":"QueriesHydration.mjs","names":[],"sources":["../src/QueriesHydration.tsx"],"sourcesContent":["import {\n Hydrate,\n type HydrateProps,\n type OmitKeyof,\n QueryClient,\n type QueryOptions,\n type UseInfiniteQueryOptions,\n type WithRequired,\n dehydrate,\n} from '@tanstack/react-query'\nimport type { ReactNode } from 'react'\nimport { ClientOnly } from './components/ClientOnly'\n\n/**\n * A server component that fetches multiple queries on the server and hydrates them to the client.\n *\n * @experimental This component is experimental and may be changed or removed in the future.\n *\n * @description\n * QueriesHydration is designed for React Server Components (RSC).\n * It pre-fetches multiple queries on the server side and automatically hydrates\n * the data to the client, enabling seamless data synchronization between server and client.\n *\n * When errors occur during server-side fetching, the component gracefully falls back\n * to client-side rendering, ensuring your application remains resilient.\n *\n * @example\n * ```tsx\n * // app/page.tsx (Server Component)\n * import { Suspense } from 'react'\n * import { QueriesHydration } from '@suspensive/react-query'\n * import { queryOptions } from '@tanstack/react-query'\n *\n * const userQueryOptions = (userId: string) => queryOptions({\n * queryKey: ['user', userId],\n * queryFn: () => fetchUser(userId)\n * })\n *\n * const postsQueryOptions = () => queryOptions({\n * queryKey: ['posts'],\n * queryFn: () => fetchPosts()\n * })\n *\n * export default function Page({ userId }: { userId: string }) {\n * return (\n * <>\n * <Suspense fallback={<div>Loading user...</div>}>\n * <QueriesHydration queries={[userQueryOptions(userId)]}>\n * <UserProfile />\n * </QueriesHydration>\n * </Suspense>\n *\n * <Suspense fallback={<div>Loading posts...</div>}>\n * <QueriesHydration queries={[postsQueryOptions()]}>\n * <PostsList />\n * </QueriesHydration>\n * </Suspense>\n * </>\n * )\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With custom error fallback\n * <Suspense fallback={<div>Loading user...</div>}>\n * <QueriesHydration\n * queries={[userQueryOptions(userId)]}\n * skipSsrOnError={{ fallback: <div>Fetching on client...</div> }}\n * >\n * <UserProfile />\n * </QueriesHydration>\n * </Suspense>\n * ```\n *\n * @see {@link https://suspensive.org/docs/react-query/QueriesHydration Documentation}\n */\nexport async function QueriesHydration({\n queries,\n children,\n queryClient = new QueryClient(),\n skipSsrOnError = true,\n timeout,\n ...props\n}: {\n /**\n * The QueryClient instance to use for fetching queries.\n */\n queryClient?: QueryClient\n /**\n * An array of query options or infinite query options to be fetched on the server. Each query must include a `queryKey`.\n * You can mix regular queries and infinite queries in the same array.\n */\n queries: (\n | WithRequired<QueryOptions<any, any, any, any>, 'queryKey'>\n | WithRequired<UseInfiniteQueryOptions<any, any, any, any, any>, 'queryKey'>\n )[]\n /**\n * Controls error handling behavior:\n * - `true` (default): Skips SSR and falls back to client-side rendering when server fetch fails\n * - `false`: Proceeds with SSR without hydration (retry fetching on client component server rendering)\n * - `{ fallback: ReactNode }`: Skips SSR with custom fallback UI during client-side rendering\n */\n skipSsrOnError?:\n | boolean\n | {\n fallback: ReactNode\n }\n /**\n * The timeout in milliseconds for the query.\n * If the query takes longer than the timeout, it will be considered as an error.\n * When not set, no timeout is applied.\n */\n timeout?: number\n} & OmitKeyof<HydrateProps, 'state'>) {\n const timeoutController = timeout != null && timeout >= 0 ? createTimeoutController(timeout) : undefined\n try {\n const queriesPromise = Promise.all(\n queries.map((query) =>\n 'getNextPageParam' in query ? queryClient.fetchInfiniteQuery(query) : queryClient.fetchQuery(query)\n )\n )\n await (timeoutController != null ? Promise.race([queriesPromise, timeoutController.promise]) : queriesPromise)\n timeoutController?.clear()\n } catch {\n timeoutController?.clear()\n queries.forEach((query) => void queryClient.cancelQueries(query))\n if (skipSsrOnError) {\n return (\n <ClientOnly fallback={skipSsrOnError === true ? undefined : skipSsrOnError.fallback}>{children}</ClientOnly>\n )\n }\n }\n return (\n <Hydrate {...props} state={dehydrate(queryClient)}>\n {children}\n </Hydrate>\n )\n}\n\nconst createTimeoutController = (ms: number) => {\n let timerId: ReturnType<typeof setTimeout> | undefined\n return {\n promise: new Promise<never>((_, reject) => {\n timerId = setTimeout(() => reject(new Error(`QueriesHydration: timeout after ${ms} ms`)), ms)\n }),\n clear: () => timerId != null && clearTimeout(timerId),\n }\n}\n"],"mappings":";;;;;;;;;CA8EE;CACA;CACA;CACA;CACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AALF,SAAsB,iBAAiB;;;;wDAqCD;MArCC,EACrC,SACA,UACA,cAAc,IAAI,YAAY,GAC9B,iBAAiB,MACjB,kBACG;EAgCH,MAAM,oBAAoB,WAAW,QAAQ,WAAW,IAAI,wBAAwB,OAAO,IAAI;EAC/F,IAAI;GACF,MAAM,iBAAiB,QAAQ,IAC7B,QAAQ,KAAK,UACX,sBAAsB,QAAQ,YAAY,mBAAmB,KAAK,IAAI,YAAY,WAAW,KAAK,CACpG,CACF;GACA,MAAO,qBAAqB,OAAO,QAAQ,KAAK,CAAC,gBAAgB,kBAAkB,OAAO,CAAC,IAAI;GAC/F,gFAAmB,MAAM;EAC3B,kBAAQ;GACN,gFAAmB,MAAM;GACzB,QAAQ,SAAS,UAAU,KAAK,YAAY,cAAc,KAAK,CAAC;GAChE,IAAI,gBACF,OACE,oBAAC,YAAD;IAAY,UAAU,mBAAmB,OAAO,SAAY,eAAe;IAAW;GAAqB;EAGjH;EACA,OACE,oBAAC,2CAAY;GAAO,OAAO,UAAU,WAAW;GAC7C;GACM;CAEb;;;AAEA,MAAM,2BAA2B,OAAe;CAC9C,IAAI;CACJ,OAAO;EACL,SAAS,IAAI,SAAgB,GAAG,WAAW;GACzC,UAAU,iBAAiB,uBAAO,IAAI,MAAM,mCAAmC,GAAG,IAAI,CAAC,GAAG,EAAE;EAC9F,CAAC;EACD,aAAa,WAAW,QAAQ,aAAa,OAAO;CACtD;AACF"}