@tanstack/react-query-next-experimental 5.0.0-alpha.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/LICENSE +21 -0
  2. package/build/lib/HydrationStreamProvider.cjs +120 -0
  3. package/build/lib/HydrationStreamProvider.cjs.map +1 -0
  4. package/build/lib/HydrationStreamProvider.d.ts +57 -0
  5. package/build/lib/HydrationStreamProvider.d.ts.map +1 -0
  6. package/build/lib/HydrationStreamProvider.js +99 -0
  7. package/build/lib/HydrationStreamProvider.js.map +1 -0
  8. package/build/lib/HydrationStreamProvider.legacy.cjs +125 -0
  9. package/build/lib/HydrationStreamProvider.legacy.cjs.map +1 -0
  10. package/build/lib/HydrationStreamProvider.legacy.js +104 -0
  11. package/build/lib/HydrationStreamProvider.legacy.js.map +1 -0
  12. package/build/lib/ReactQueryStreamedHydration.cjs +89 -0
  13. package/build/lib/ReactQueryStreamedHydration.cjs.map +1 -0
  14. package/build/lib/ReactQueryStreamedHydration.d.ts +18 -0
  15. package/build/lib/ReactQueryStreamedHydration.d.ts.map +1 -0
  16. package/build/lib/ReactQueryStreamedHydration.js +68 -0
  17. package/build/lib/ReactQueryStreamedHydration.js.map +1 -0
  18. package/build/lib/ReactQueryStreamedHydration.legacy.cjs +91 -0
  19. package/build/lib/ReactQueryStreamedHydration.legacy.cjs.map +1 -0
  20. package/build/lib/ReactQueryStreamedHydration.legacy.js +70 -0
  21. package/build/lib/ReactQueryStreamedHydration.legacy.js.map +1 -0
  22. package/build/lib/index.cjs +8 -0
  23. package/build/lib/index.cjs.map +1 -0
  24. package/build/lib/index.d.ts +2 -0
  25. package/build/lib/index.d.ts.map +1 -0
  26. package/build/lib/index.js +2 -0
  27. package/build/lib/index.js.map +1 -0
  28. package/build/lib/index.legacy.cjs +8 -0
  29. package/build/lib/index.legacy.cjs.map +1 -0
  30. package/build/lib/index.legacy.js +2 -0
  31. package/build/lib/index.legacy.js.map +1 -0
  32. package/package.json +57 -0
  33. package/src/HydrationStreamProvider.tsx +184 -0
  34. package/src/ReactQueryStreamedHydration.tsx +93 -0
  35. package/src/index.ts +1 -0
@@ -0,0 +1,184 @@
1
+ 'use client'
2
+
3
+ import { useServerInsertedHTML } from 'next/navigation'
4
+ import * as React from 'react'
5
+
6
+ const serializedSymbol = Symbol('serialized')
7
+
8
+ interface DataTransformer {
9
+ serialize(object: any): any
10
+ deserialize(object: any): any
11
+ }
12
+
13
+ type Serialized<TData> = unknown & {
14
+ [serializedSymbol]: TData
15
+ }
16
+
17
+ interface TypedDataTransformer<TData> {
18
+ serialize: (obj: TData) => Serialized<TData>
19
+ deserialize: (obj: Serialized<TData>) => TData
20
+ }
21
+
22
+ interface HydrationStreamContext<TShape> {
23
+ id: string
24
+ stream: {
25
+ /**
26
+ * **Server method**
27
+ * Push a new entry to the stream
28
+ * Will be ignored on the client
29
+ */
30
+ push: (...shape: TShape[]) => void
31
+ }
32
+ }
33
+
34
+ export interface HydrationStreamProviderProps<TShape> {
35
+ children: React.ReactNode
36
+ /**
37
+ * Optional transformer to serialize/deserialize the data
38
+ * Example devalue, superjson et al
39
+ */
40
+ transformer?: DataTransformer
41
+ /**
42
+ * **Client method**
43
+ * Called in the browser when new entries are received
44
+ */
45
+ onEntries: (entries: TShape[]) => void
46
+ /**
47
+ * **Server method**
48
+ * onFlush is called on the server when the cache is flushed
49
+ */
50
+ onFlush?: () => TShape[]
51
+ }
52
+
53
+ export function createHydrationStreamProvider<TShape>() {
54
+ const context = React.createContext<HydrationStreamContext<TShape>>(
55
+ null as any,
56
+ )
57
+ /**
58
+
59
+ * 1. (Happens on server): `useServerInsertedHTML()` is called **on the server** whenever a `Suspense`-boundary completes
60
+ * - This means that we might have some new entries in the cache that needs to be flushed
61
+ * - We pass these to the client by inserting a `<script>`-tag where we do `window[id].push(serializedVersionOfCache)`
62
+ * 2. (Happens in browser) In `useEffect()`:
63
+ * - We check if `window[id]` is set to an array and call `push()` on all the entries which will call `onEntries()` with the new entries
64
+ * - We replace `window[id]` with a `push()`-method that will be called whenever new entries are received
65
+ **/
66
+ function UseClientHydrationStreamProvider(props: {
67
+ children: React.ReactNode
68
+ /**
69
+ * Optional transformer to serialize/deserialize the data
70
+ * Example devalue, superjson et al
71
+ */
72
+ transformer?: DataTransformer
73
+ /**
74
+ * **Client method**
75
+ * Called in the browser when new entries are received
76
+ */
77
+ onEntries: (entries: TShape[]) => void
78
+ /**
79
+ * **Server method**
80
+ * onFlush is called on the server when the cache is flushed
81
+ */
82
+ onFlush?: () => TShape[]
83
+ }) {
84
+ // unique id for the cache provider
85
+ const id = `__RQ${React.useId()}`
86
+ const idJSON = JSON.stringify(id)
87
+
88
+ const [transformer] = React.useState(
89
+ () =>
90
+ (props.transformer ?? {
91
+ // noop
92
+ serialize: (obj: any) => obj,
93
+ deserialize: (obj: any) => obj,
94
+ }) as unknown as TypedDataTransformer<TShape>,
95
+ )
96
+
97
+ // <server stuff>
98
+ const [stream] = React.useState<TShape[]>(() => {
99
+ if (typeof window !== 'undefined') {
100
+ return {
101
+ push() {
102
+ // no-op on the client
103
+ },
104
+ } as unknown as TShape[]
105
+ }
106
+ return []
107
+ })
108
+ const count = React.useRef(0)
109
+ useServerInsertedHTML(() => {
110
+ // This only happens on the server
111
+ stream.push(...(props.onFlush?.() ?? []))
112
+
113
+ if (!stream.length) {
114
+ return null
115
+ }
116
+ // console.log(`pushing ${stream.length} entries`)
117
+ const serializedCacheArgs = stream
118
+ .map((entry) => transformer.serialize(entry))
119
+ .map((entry) => JSON.stringify(entry))
120
+ .join(',')
121
+
122
+ // Flush stream
123
+ stream.length = 0
124
+
125
+ const html: string[] = [
126
+ `window[${idJSON}] = window[${idJSON}] || [];`,
127
+ `window[${idJSON}].push(${serializedCacheArgs});`,
128
+ ]
129
+ return (
130
+ <script
131
+ key={count.current++}
132
+ dangerouslySetInnerHTML={{
133
+ __html: html.join(''),
134
+ }}
135
+ />
136
+ )
137
+ })
138
+ // </server stuff>
139
+
140
+ // <client stuff>
141
+ const onEntriesRef = React.useRef(props.onEntries)
142
+ React.useEffect(() => {
143
+ onEntriesRef.current = props.onEntries
144
+ })
145
+
146
+ React.useEffect(() => {
147
+ // Client: consume cache:
148
+ const onEntries = (...serializedEntries: Serialized<TShape>[]) => {
149
+ const entries = serializedEntries.map((serialized) =>
150
+ transformer.deserialize(serialized),
151
+ )
152
+ onEntriesRef.current(entries)
153
+ }
154
+
155
+ const win = window as any
156
+ // Register cache consumer
157
+ const winStream: Array<Serialized<TShape>> = win[id] ?? []
158
+
159
+ onEntries(...winStream)
160
+
161
+ // Register our own consumer
162
+ win[id] = {
163
+ push: onEntries,
164
+ }
165
+
166
+ return () => {
167
+ // Cleanup after unmount
168
+ win[id] = []
169
+ }
170
+ }, [id, transformer])
171
+ // </client stuff>
172
+
173
+ return (
174
+ <context.Provider value={{ stream, id }}>
175
+ {props.children}
176
+ </context.Provider>
177
+ )
178
+ }
179
+
180
+ return {
181
+ Provider: UseClientHydrationStreamProvider,
182
+ context,
183
+ }
184
+ }
@@ -0,0 +1,93 @@
1
+ 'use client'
2
+
3
+ import type {
4
+ DehydratedState,
5
+ DehydrateOptions,
6
+ HydrateOptions,
7
+ QueryClient,
8
+ } from '@tanstack/react-query'
9
+ import {
10
+ defaultShouldDehydrateQuery,
11
+ dehydrate,
12
+ hydrate,
13
+ useQueryClient,
14
+ } from '@tanstack/react-query'
15
+ import * as React from 'react'
16
+ import type { HydrationStreamProviderProps } from './HydrationStreamProvider'
17
+ import { createHydrationStreamProvider } from './HydrationStreamProvider'
18
+
19
+ const stream = createHydrationStreamProvider<DehydratedState>()
20
+
21
+ /**
22
+ * This component is responsible for:
23
+ * - hydrating the query client on the server
24
+ * - dehydrating the query client on the server
25
+ */
26
+ export function ReactQueryStreamedHydration(props: {
27
+ children: React.ReactNode
28
+ queryClient?: QueryClient
29
+ options?: {
30
+ hydrate?: HydrateOptions
31
+ dehydrate?: DehydrateOptions
32
+ }
33
+ transformer?: HydrationStreamProviderProps<DehydratedState>['transformer']
34
+ }) {
35
+ const queryClient = useQueryClient(props.queryClient)
36
+
37
+ /**
38
+ * We need to track which queries were added/updated during the render
39
+ */
40
+ const [trackedKeys] = React.useState(() => new Set<string>())
41
+
42
+ // <server only>
43
+ if (typeof window === 'undefined') {
44
+ // Do we need to care about unsubscribing? I don't think so to be honest
45
+ queryClient.getQueryCache().subscribe((event) => {
46
+ switch (event.type) {
47
+ case 'added':
48
+ case 'updated':
49
+ // console.log('tracking', event.query.queryHash, 'b/c of a', event.type)
50
+ trackedKeys.add(event.query.queryHash)
51
+ }
52
+ })
53
+ }
54
+ // </server only>
55
+
56
+ return (
57
+ <stream.Provider
58
+ // Happens on server:
59
+ onFlush={() => {
60
+ /**
61
+ * Dehydrated state of the client where we only include the queries that were added/updated since the last flush
62
+ */
63
+ const shouldDehydrate =
64
+ props.options?.dehydrate?.shouldDehydrateQuery ??
65
+ defaultShouldDehydrateQuery
66
+
67
+ const dehydratedState = dehydrate(queryClient, {
68
+ ...props.options?.dehydrate,
69
+ shouldDehydrateQuery(query) {
70
+ return trackedKeys.has(query.queryHash) && shouldDehydrate(query)
71
+ },
72
+ })
73
+ trackedKeys.clear()
74
+
75
+ if (!dehydratedState.queries.length) {
76
+ return []
77
+ }
78
+
79
+ return [dehydratedState]
80
+ }}
81
+ // Happens in browser:
82
+ onEntries={(entries) => {
83
+ for (const hydratedState of entries) {
84
+ hydrate(queryClient, hydratedState, props.options?.hydrate)
85
+ }
86
+ }}
87
+ // Handle BigInts etc using superjson
88
+ transformer={props.transformer}
89
+ >
90
+ {props.children}
91
+ </stream.Provider>
92
+ )
93
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { ReactQueryStreamedHydration } from './ReactQueryStreamedHydration'