@@ -188,6 +188,50 @@ function escapeStringValue(value: string): string {
188
188
}
189
189
}
190
190
191
+ function isObjectPrototype ( object ) : boolean {
192
+ if ( ! object ) {
193
+ return false ;
194
+ }
195
+ // $FlowFixMe
196
+ const ObjectPrototype = Object . prototype ;
197
+ if ( object === ObjectPrototype ) {
198
+ return true ;
199
+ }
200
+ // It might be an object from a different Realm which is
201
+ // still just a plain simple object.
202
+ if ( Object . getPrototypeOf ( object ) ) {
203
+ return false ;
204
+ }
205
+ const names = Object . getOwnPropertyNames ( object ) ;
206
+ for ( let i = 0 ; i < names . length ; i ++ ) {
207
+ if ( ! ( names [ i ] in ObjectPrototype ) ) {
208
+ return false ;
209
+ }
210
+ }
211
+ return true ;
212
+ }
213
+
214
+ function isSimpleObject ( object ) : boolean {
215
+ if ( ! isObjectPrototype ( Object . getPrototypeOf ( object ) ) ) {
216
+ return false ;
217
+ }
218
+ const names = Object . getOwnPropertyNames ( object ) ;
219
+ for ( let i = 0 ; i < names . length ; i ++ ) {
220
+ const descriptor = Object . getOwnPropertyDescriptor ( object , names [ i ] ) ;
221
+ if ( ! descriptor || ! descriptor . enumerable ) {
222
+ return false ;
223
+ }
224
+ }
225
+ return true ;
226
+ }
227
+
228
+ function objectName ( object ) : string {
229
+ const name = Object . prototype . toString . call ( object ) ;
230
+ return name . replace ( / ^ \[ o b j e c t ( .* ) \] $ / , function ( m , p0 ) {
231
+ return p0 ;
232
+ } ) ;
233
+ }
234
+
191
235
function describeKeyForErrorMessage ( key : string ) : string {
192
236
const encodedKey = JSON . stringify ( key ) ;
193
237
return '"' + key + '"' === encodedKey ? key : encodedKey ;
@@ -204,13 +248,10 @@ function describeValueForErrorMessage(value: ReactModel): string {
204
248
if ( isArray ( value ) ) {
205
249
return '[...]' ;
206
250
}
207
- let name = Object . prototype . toString . call ( value ) ;
251
+ const name = objectName ( value ) ;
208
252
if ( name === '[object Object]' ) {
209
253
return '{...}' ;
210
254
}
211
- name = name . replace ( / ^ \[ o b j e c t ( .* ) \] $ / , function ( m , p0 ) {
212
- return p0 ;
213
- } ) ;
214
255
return name ;
215
256
}
216
257
case 'function' :
@@ -246,7 +287,7 @@ function describeObjectForErrorMessage(
246
287
let str = '{' ;
247
288
// $FlowFixMe: Should be refined by now.
248
289
const object : { + [ key : string | number ] : ReactModel } = objectOrArray ;
249
- const names = Object . getOwnPropertyNames ( object ) ;
290
+ const names = Object . keys ( object ) ;
250
291
for ( let i = 0 ; i < names . length ; i ++ ) {
251
292
if ( i > 0 ) {
252
293
str += ', ' ;
@@ -272,6 +313,21 @@ export function resolveModelToJSON(
272
313
key : string ,
273
314
value : ReactModel ,
274
315
) : ReactJSONValue {
316
+ if ( __DEV__ ) {
317
+ // $FlowFixMe
318
+ const originalValue = parent [ key ] ;
319
+ if ( typeof originalValue === 'object' && originalValue !== value ) {
320
+ console . error (
321
+ 'Only plain objects can be passed to client components from server components. ' +
322
+ 'Objects with toJSON methods are not supported. Convert it manually ' +
323
+ 'to a simple value before passing it to props. ' +
324
+ 'Remove %s from these props: %s %s' ,
325
+ describeKeyForErrorMessage ( key ) ,
326
+ describeObjectForErrorMessage ( parent ) ,
327
+ ) ;
328
+ }
329
+ }
330
+
275
331
// Special Symbols
276
332
switch ( value ) {
277
333
case REACT_ELEMENT_TYPE :
@@ -371,8 +427,38 @@ export function resolveModelToJSON(
371
427
372
428
if ( typeof value === 'object' ) {
373
429
if ( __DEV__ ) {
374
- if ( value !== null ) {
375
- return value ;
430
+ if ( value !== null && ! isArray ( value ) ) {
431
+ // Verify that this is a simple plain object.
432
+ if ( objectName ( value ) !== 'Object' ) {
433
+ console . error (
434
+ 'Only plain objects can be passed to client components from server components. ' +
435
+ 'Built-ins like %s are not supported. ' +
436
+ 'Remove %s from these props: %s' ,
437
+ objectName ( value ) ,
438
+ describeKeyForErrorMessage ( key ) ,
439
+ describeObjectForErrorMessage ( parent ) ,
440
+ ) ;
441
+ } else if ( ! isSimpleObject ( value ) ) {
442
+ console . error (
443
+ 'Only plain objects can be passed to client components from server components. ' +
444
+ 'Classes or other objects with methods are not supported. ' +
445
+ 'Remove %s from these props: %s' ,
446
+ describeKeyForErrorMessage ( key ) ,
447
+ describeObjectForErrorMessage ( parent ) ,
448
+ ) ;
449
+ } else if ( Object . getOwnPropertySymbols ) {
450
+ const symbols = Object . getOwnPropertySymbols ( value ) ;
451
+ if ( symbols . length > 0 ) {
452
+ console . error (
453
+ 'Only plain objects can be passed to client components from server components. ' +
454
+ 'Objects with symbol properties like %s are not supported. ' +
455
+ 'Remove %s from these props: %s' ,
456
+ symbols [ 0 ] . description ,
457
+ describeKeyForErrorMessage ( key ) ,
458
+ describeObjectForErrorMessage ( parent ) ,
459
+ ) ;
460
+ }
461
+ }
376
462
}
377
463
}
378
464
return value ;
0 commit comments