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