@@ -14,6 +14,8 @@ import type {
1414 RejectedThenable ,
1515 ReactCustomFormAction ,
1616} from 'shared/ReactTypes' ;
17+ import type { LazyComponent } from 'react/src/ReactLazy' ;
18+
1719import { enableRenderableContext } from 'shared/ReactFeatureFlags' ;
1820
1921import {
@@ -84,9 +86,9 @@ export type ReactServerValue =
8486
8587type ReactServerObject = { + [ key : string ] : ReactServerValue } ;
8688
87- // function serializeByValueID(id: number): string {
88- // return '$' + id.toString(16);
89- // }
89+ function serializeByValueID(id: number): string {
90+ return '$' + id . toString ( 16 ) ;
91+ }
9092
9193function serializePromiseID(id: number): string {
9294 return '$@' + id . toString ( 16 ) ;
@@ -206,6 +208,78 @@ export function processReply(
206208 }
207209
208210 if (typeof value === 'object') {
211+ switch ( ( value : any ) . $$typeof ) {
212+ case REACT_ELEMENT_TYPE : {
213+ throw new Error (
214+ 'React Element cannot be passed to Server Functions from the Client.' +
215+ ( __DEV__ ? describeObjectForErrorMessage ( parent , key ) : '' ) ,
216+ ) ;
217+ }
218+ case REACT_LAZY_TYPE: {
219+ // Resolve lazy as if it wasn't here. In the future this will be encoded as a Promise.
220+ const lazy : LazyComponent < any , any > = ( value : any ) ;
221+ const payload = lazy . _payload ;
222+ const init = lazy . _init ;
223+ if ( formData === null ) {
224+ // Upgrade to use FormData to allow us to stream this value.
225+ formData = new FormData ( ) ;
226+ }
227+ try {
228+ const resolvedModel = init ( payload ) ;
229+ // We always outline this as a separate part even though we could inline it
230+ // because it ensures a more deterministic encoding.
231+ pendingParts ++ ;
232+ const lazyId = nextPartId ++ ;
233+ const partJSON = JSON . stringify ( resolvedModel , resolveToJSON ) ;
234+ // $FlowFixMe[incompatible-type] We know it's not null because we assigned it above.
235+ const data : FormData = formData ;
236+ // eslint-disable-next-line react-internal/safe-string-coercion
237+ data . append ( formFieldPrefix + lazyId , partJSON ) ;
238+ pendingParts -- ;
239+ return serializeByValueID ( lazyId ) ;
240+ } catch ( x ) {
241+ if (
242+ typeof x === 'object' &&
243+ x !== null &&
244+ typeof x . then === 'function'
245+ ) {
246+ // Suspended
247+ pendingParts ++ ;
248+ const lazyId = nextPartId ++ ;
249+ const thenable : Thenable < any > = (x: any);
250+ thenable.then(
251+ partValue => {
252+ try {
253+ const partJSON = JSON . stringify ( partValue , resolveToJSON ) ;
254+ // $FlowFixMe[incompatible-type] We know it's not null because we assigned it above.
255+ const data : FormData = formData ;
256+ // eslint-disable-next-line react-internal/safe-string-coercion
257+ data . append ( formFieldPrefix + lazyId , partJSON ) ;
258+ pendingParts -- ;
259+ if ( pendingParts === 0 ) {
260+ resolve ( data ) ;
261+ }
262+ } catch ( reason ) {
263+ reject ( reason ) ;
264+ }
265+ } ,
266+ reason => {
267+ // In the future we could consider serializing this as an error
268+ // that throws on the server instead.
269+ reject ( reason ) ;
270+ } ,
271+ );
272+ return serializeByValueID(lazyId);
273+ } else {
274+ // In the future we could consider serializing this as an error
275+ // that throws on the server instead.
276+ reject ( x ) ;
277+ return null ;
278+ }
279+ }
280+ }
281+ }
282+
209283 // $FlowFixMe[method-unbinding]
210284 if (typeof value.then === 'function') {
211285 // We assume that any object with a .then property is a "Thenable" type,
@@ -219,14 +293,18 @@ export function processReply(
219293 const thenable: Thenable< any > = (value: any);
220294 thenable.then(
221295 partValue => {
222- const partJSON = JSON . stringify ( partValue , resolveToJSON ) ;
223- // $FlowFixMe[incompatible-type] We know it's not null because we assigned it above.
224- const data : FormData = formData ;
225- // eslint-disable-next-line react-internal/safe-string-coercion
226- data . append ( formFieldPrefix + promiseId , partJSON ) ;
227- pendingParts -- ;
228- if ( pendingParts === 0 ) {
229- resolve ( data ) ;
296+ try {
297+ const partJSON = JSON . stringify ( partValue , resolveToJSON ) ;
298+ // $FlowFixMe[incompatible-type] We know it's not null because we assigned it above.
299+ const data : FormData = formData ;
300+ // eslint-disable-next-line react-internal/safe-string-coercion
301+ data . append ( formFieldPrefix + promiseId , partJSON ) ;
302+ pendingParts -- ;
303+ if ( pendingParts === 0 ) {
304+ resolve ( data ) ;
305+ }
306+ } catch ( reason ) {
307+ reject ( reason ) ;
230308 }
231309 } ,
232310 reason => {
@@ -294,17 +372,7 @@ export function processReply(
294372 ) ;
295373 }
296374 if (__DEV__) {
297- if ( ( value : any ) . $$typeof === REACT_ELEMENT_TYPE ) {
298- console . error (
299- 'React Element cannot be passed to Server Functions from the Client.%s' ,
300- describeObjectForErrorMessage ( parent , key ) ,
301- ) ;
302- } else if ((value: any).$$typeof === REACT_LAZY_TYPE) {
303- console . error (
304- 'React Lazy cannot be passed to Server Functions from the Client.%s' ,
305- describeObjectForErrorMessage ( parent , key ) ,
306- ) ;
307- } else if (
375+ if (
308376 ( value : any ) . $$typeof ===
309377 ( enableRenderableContext ? REACT_CONTEXT_TYPE : REACT_PROVIDER_TYPE )
310378 ) {
0 commit comments