1
1
import type React from 'react'
2
- import { useState , useEffect } from 'react'
2
+ import { use , useState , useEffect } from 'react'
3
3
4
4
import type { Options } from 'react-server-dom-webpack/client'
5
5
import { createFromFetch , encodeReply } from 'react-server-dom-webpack/client'
@@ -17,27 +17,48 @@ export interface RscProps extends Record<string, unknown> {
17
17
}
18
18
}
19
19
20
- export function rscFetch (
21
- rscId : string ,
22
- serializedProps : string ,
23
- // setComponent?: (component: Thenable<React.ReactElement>) => void,
20
+ let updateCurrentRscCacheKey = ( key : string ) => {
21
+ console . error ( 'updateCurrentRscCacheKey called before it was set' )
22
+ console . error ( 'updateCurrentRscCacheKey key' , key )
23
+ }
24
+
25
+ function onStreamFinished (
26
+ fetchPromise : ReturnType < typeof fetch > ,
27
+ onFinished : ( text : string ) => void ,
24
28
) {
25
- console . log ( 'rscFetch :: rscId' , rscId )
26
- console . log ( 'rscFetch :: props' , serializedProps )
29
+ return (
30
+ fetchPromise
31
+ // clone the response so createFromFetch can use it (otherwise we lock the
32
+ // reader) and wait for the text to be consumed so we know the stream is
33
+ // finished
34
+ . then ( ( response ) => response . clone ( ) . text ( ) )
35
+ . then ( onFinished )
36
+ )
37
+ }
38
+
39
+ function rscFetch ( rscId : string , serializedProps : string ) {
40
+ console . log (
41
+ 'rscFetch :: args:\n rscId: ' +
42
+ rscId +
43
+ '\n serializedProps: ' +
44
+ serializedProps ,
45
+ )
46
+ const rscCacheKey = `${ rscId } ::${ serializedProps } `
27
47
28
- // TODO (RSC): The cache key should be rscId + serializedProps
29
- const cached = rscCache . get ( serializedProps )
48
+ const cached = rscCache . get ( rscCacheKey )
30
49
if ( cached ) {
31
- console . log ( 'rscFetch :: cache hit for' , serializedProps )
50
+ console . log ( 'rscFetch :: cache hit for' , rscCacheKey )
32
51
return cached
52
+ } else {
53
+ console . log ( 'rscFetch :: cache miss for' , rscCacheKey )
33
54
}
34
55
35
56
const searchParams = new URLSearchParams ( )
36
57
searchParams . set ( 'props' , serializedProps )
37
58
38
59
// TODO (RSC): During SSR we should not fetch (Is this function really
39
60
// called during SSR?)
40
- const response = fetch ( BASE_PATH + rscId + '?' + searchParams , {
61
+ const responsePromise = fetch ( BASE_PATH + rscId + '?' + searchParams , {
41
62
headers : {
42
63
'rw-rsc' : '1' ,
43
64
} ,
@@ -52,9 +73,16 @@ export function rscFetch(
52
73
callServer : async function ( rsaId : string , args : unknown [ ] ) {
53
74
// `args` is often going to be an array with just a single element,
54
75
// and that element will be FormData
55
- console . log ( 'rscFetchForClientRouter.ts :: callServer' )
56
- console . log ( ' rsaId' , rsaId )
57
- console . log ( ' args' , args )
76
+ console . log ( 'RscFetcher :: callServer rsaId' , rsaId , 'args' , args )
77
+
78
+ // Including rsaId here to make sure the page rerenders when calling RSAs
79
+ // Calling a RSA doesn't change the url (i.e. `serializedProps`), and it
80
+ // also doesn't change the rscId, so React would not detect a state change
81
+ // that would trigger a rerender. So we include the rsaId here to make
82
+ // a new cache key that will trigger a rerender.
83
+ // TODO (RSC): What happens if you call the same RSA twice in a row?
84
+ // Like `increment()`
85
+ const rscCacheKey = `${ rscId } ::${ serializedProps } ::${ rsaId } ::${ new Date ( ) } `
58
86
59
87
const searchParams = new URLSearchParams ( )
60
88
searchParams . set ( 'action_id' , rsaId )
@@ -69,31 +97,37 @@ export function rscFetch(
69
97
console . error ( 'Error encoding Server Action arguments' , e )
70
98
}
71
99
72
- const response = fetch ( BASE_PATH + id + '?' + searchParams , {
100
+ const responsePromise = fetch ( BASE_PATH + id + '?' + searchParams , {
73
101
method : 'POST' ,
74
102
body,
75
103
headers : {
76
104
'rw-rsc' : '1' ,
77
105
} ,
78
106
} )
79
107
108
+ onStreamFinished ( responsePromise , ( ) => {
109
+ updateCurrentRscCacheKey ( rscCacheKey )
110
+ } )
111
+
80
112
// I'm not sure this recursive use of `options` is needed. I briefly
81
113
// tried without it, and things seemed to work. But keeping it for
82
114
// now, until we learn more.
83
- const data = createFromFetch ( response , options )
115
+ const dataPromise = createFromFetch ( responsePromise , options )
84
116
85
- const dataValue = await data
86
- console . log (
87
- 'rscFetchForClientRuoter.ts :: callServer dataValue' ,
88
- dataValue ,
89
- )
117
+ // TODO (RSC): This is where we want to update the RSA cache, but first we
118
+ // need to normalize the data that comes back from the server. We need to
119
+ // always send an object with a `__rwjs__rsa_data` key and some key
120
+ // for the flight data
121
+ // rscCache.set(rscCacheKey, dataPromise)
122
+
123
+ const dataValue = await dataPromise
124
+ console . log ( 'RscFetcher :: callServer dataValue' , dataValue )
90
125
// TODO (RSC): Fix the types for `createFromFetch`
91
126
// @ts -expect-error The type is wrong for createFromFetch
92
127
const Routes = dataValue . Routes ?. [ 0 ]
93
128
console . log ( 'Routes' , Routes )
94
129
95
- // TODO (RSC): Figure out how to trigger a rerender of the page with the
96
- // new Routes
130
+ rscCache . set ( rscCacheKey , Promise . resolve ( Routes ) )
97
131
98
132
// TODO (RSC): Fix the types for `createFromFetch`
99
133
// @ts -expect-error The type is wrong for createFromFetch. It can really
@@ -104,12 +138,14 @@ export function rscFetch(
104
138
}
105
139
106
140
const componentPromise = createFromFetch < never , React . ReactElement > (
107
- response ,
141
+ responsePromise ,
108
142
options ,
109
143
)
110
144
111
- rscCache . set ( serializedProps , componentPromise )
145
+ rscCache . set ( rscCacheKey , componentPromise )
112
146
147
+ // TODO (RSC): Figure out if this is ever used, or if it's better to return
148
+ // the cache key
113
149
return componentPromise
114
150
}
115
151
@@ -120,25 +156,43 @@ interface Props {
120
156
121
157
export const RscFetcher = ( { rscId, rscProps } : Props ) => {
122
158
const serializedProps = JSON . stringify ( rscProps )
123
- const [ component , setComponent ] = useState < any > ( ( ) => {
124
- console . log ( 'RscFetcher :: useState callback' )
125
-
126
- return rscFetch ( rscId , serializedProps )
159
+ const [ currentRscCacheKey , setCurrentRscCacheKey ] = useState ( ( ) => {
160
+ console . log ( 'RscFetcher :: useState initial value' )
161
+ // Calling rscFetch here to prime the cache
162
+ rscFetch ( rscId , serializedProps )
163
+ return `${ rscId } ::${ serializedProps } `
127
164
} )
128
165
129
- console . log ( 'RscFetcher rerender rscId' , rscId )
130
- console . log ( 'RscFetcher rerender rscProps' , rscProps )
166
+ useEffect ( ( ) => {
167
+ console . log ( 'RscFetcher :: useEffect set updateCurrentRscCacheKey' )
168
+ updateCurrentRscCacheKey = ( key : string ) => {
169
+ console . log ( 'RscFetcher inside updateCurrentRscCacheKey' , key )
131
170
132
- if ( ! rscCache . get ( serializedProps ) ) {
133
- rscFetch ( rscId , serializedProps )
134
- }
171
+ setCurrentRscCacheKey ( key )
172
+ }
173
+ } , [ ] )
135
174
136
175
useEffect ( ( ) => {
137
- console . log ( 'RscFetcher :: useEffect rscProps ' )
138
- const componentPromise = rscFetch ( rscId , serializedProps )
139
- console . log ( 'componentPromise' , componentPromise )
140
- setComponent ( componentPromise )
176
+ console . log ( 'RscFetcher :: useEffect about to call rscFetch ' )
177
+ // rscFetch will update rscCache with the fetched component
178
+ rscFetch ( rscId , serializedProps )
179
+ setCurrentRscCacheKey ( ` ${ rscId } :: ${ serializedProps } ` )
141
180
} , [ rscId , serializedProps ] )
142
181
143
- return component
182
+ console . log (
183
+ 'RscFetcher :: current props\n' +
184
+ ' rscId: ' +
185
+ rscId +
186
+ '\n rscProps: ' +
187
+ serializedProps ,
188
+ )
189
+ console . log ( 'RscFetcher :: rendering cache entry for\n' + currentRscCacheKey )
190
+
191
+ const component = rscCache . get ( currentRscCacheKey )
192
+
193
+ if ( ! component ) {
194
+ throw new Error ( 'Missing RSC cache entry for ' + currentRscCacheKey )
195
+ }
196
+
197
+ return use ( component )
144
198
}
0 commit comments