137
| boolean
138
| number
139
| null
140
- | $ReadOnlyArray<ReactJSONValue>
140
+ | $ReadOnlyArray<ReactClientValue>
141
| ReactClientObject;
142
143
// Serializable values
180
status: 0 | 1 | 3 | 4,
181
model: ReactClientValue,
182
ping: () => void,
183
+ toJSON: (key: string, value: ReactClientValue) => ReactJSONValue,
184
context: ContextSnapshot,
185
thenableState: ThenableState | null,
186
};
213
taintCleanupQueue: Array<string | bigint>,
214
onError: (error: mixed) => ?string,
215
onPostpone: (reason: string) => void,
215
- toJSON: (key: string, value: ReactClientValue) => ReactJSONValue,
216
};
217
218
const {
311
taintCleanupQueue: cleanupQueue,
312
onError: onError === undefined ? defaultErrorHandler : onError,
313
onPostpone: onPostpone === undefined ? defaultPostponeHandler : onPostpone,
314
- // $FlowFixMe[missing-this-annot]
315
- toJSON: function (key: string, value: ReactClientValue): ReactJSONValue {
316
- return resolveModelToJSON(request, this, key, value);
317
- },
314
};
315
request.pendingChunks++;
316
const rootContext = createRootContext(context);
500
return lazyType;
501
}
502
507
-function attemptResolveElement(
503
+function renderElement(
504
request: Request,
505
+ task: Task,
506
type: any,
507
key: null | React$Key,
508
ref: mixed,
509
props: any,
510
prevThenableState: ThenableState | null,
514
-): ReactClientValue {
511
+): ReactJSONValue {
512
if (ref !== null && ref !== undefined) {
513
// When the ref moves to the regular props object this will implicitly
514
// throw for functions. We could probably relax it to a DEV warning for other
530
}
531
// This is a server-side component.
532
prepareToUseHooksForComponent(prevThenableState);
536
- const result = type(props);
533
+ let result = type(props);
534
if (
535
typeof result === 'object' &&
536
result !== null &&
544
}
545
// TODO: Once we accept Promises as children on the client, we can just return
546
// the thenable here.
550
- return createLazyWrapperAroundWakeable(result);
547
+ result = createLazyWrapperAroundWakeable(result);
548
}
552
- return result;
549
+ return renderModelDestructive(request, task, emptyRoot, '', result, null);
550
} else if (typeof type === 'string') {
551
// This is a host element. E.g. HTML.
552
return [REACT_ELEMENT_TYPE, type, key, props];
556
// it as a wrapper.
557
// TODO: If a key is specified, we should propagate its key to any children.
558
// Same as if a Server Component has a key.
562
- return props.children;
559
+ return renderModelDestructive(
560
+ request,
561
+ task,
562
+ emptyRoot,
563
+ '',
564
+ props.children,
565
+ null,
566
+ );
567
}
568
// This might be a built-in React component. We'll let the client decide.
569
// Any built-in works as long as its props are serializable.
578
const payload = type._payload;
579
const init = type._init;
580
const wrappedType = init(payload);
577
- return attemptResolveElement(
581
+ return renderElement(
582
request,
583
+ task,
584
wrappedType,
585
key,
586
ref,
591
case REACT_FORWARD_REF_TYPE: {
592
const render = type.render;
593
prepareToUseHooksForComponent(prevThenableState);
589
- return render(props, undefined);
594
+ const result = render(props, undefined);
595
+ return renderModelDestructive(
596
+ request,
597
+ task,
598
+ emptyRoot,
599
+ '',
600
+ result,
601
+ null,
602
+ );
603
}
604
case REACT_MEMO_TYPE: {
592
- return attemptResolveElement(
605
+ return renderElement(
606
request,
607
+ task,
608
type.type,
609
key,
610
ref,
614
}
615
case REACT_PROVIDER_TYPE: {
616
if (enableServerContext) {
603
- pushProvider(type._context, props.value);
617
+ task.context = pushProvider(type._context, props.value);
618
if (__DEV__) {
619
const extraKeys = Object.keys(props).filter(value => {
620
if (value === 'children' || value === 'value') {
662
abortSet: Set<Task>,
663
): Task {
664
const id = request.nextChunkId++;
665
+ if (typeof model === 'object' && model !== null) {
666
+ // Register this model as having the ID we're about to write.
667
+ request.writtenObjects.set(model, id);
668
+ }
669
const task: Task = {
670
id,
671
status: PENDING,
672
model,
673
context,
674
ping: () => pingTask(request, task),
675
+ toJSON: function (
676
+ this:
677
+ | {+[key: string | number]: ReactClientValue}
678
+ | $ReadOnlyArray<ReactClientValue>,
679
+ parentPropertyName: string,
680
+ value: ReactClientValue,
681
+ ): ReactJSONValue {
682
+ const parent = this;
683
+ // Make sure that `parent[parentPropertyName]` wasn't JSONified before `value` was passed to us
684
+ if (__DEV__) {
685
+ // $FlowFixMe[incompatible-use]
686
+ const originalValue = parent[parentPropertyName];
687
+ if (
688
+ typeof originalValue === 'object' &&
689
+ originalValue !== value &&
690
+ !(originalValue instanceof Date)
691
+ ) {
692
+ if (objectName(originalValue) !== 'Object') {
693
+ const jsxParentType = jsxChildrenParents.get(parent);
694
+ if (typeof jsxParentType === 'string') {
695
+ console.error(
696
+ '%s objects cannot be rendered as text children. Try formatting it using toString().%s',
697
+ objectName(originalValue),
698
+ describeObjectForErrorMessage(parent, parentPropertyName),
699
+ );
700
+ } else {
701
+ console.error(
702
+ 'Only plain objects can be passed to Client Components from Server Components. ' +
703
+ '%s objects are not supported.%s',
704
+ objectName(originalValue),
705
+ describeObjectForErrorMessage(parent, parentPropertyName),
706
+ );
707
+ }
708
+ } else {
709
+ console.error(
710
+ 'Only plain objects can be passed to Client Components from Server Components. ' +
711
+ 'Objects with toJSON methods are not supported. Convert it manually ' +
712
+ 'to a simple value before passing it to props.%s',
713
+ describeObjectForErrorMessage(parent, parentPropertyName),
714
+ );
715
+ }
716
+ }
717
+
718
+ if (
719
+ enableServerContext &&
720
+ parent[0] === REACT_ELEMENT_TYPE &&
721
+ parent[1] &&
722
+ (parent[1]: any).$$typeof === REACT_PROVIDER_TYPE &&
723
+ parentPropertyName === '3'
724
+ ) {
725
+ insideContextProps = value;
726
+ } else if (
727
+ insideContextProps === parent &&
728
+ parentPropertyName === 'value'
729
+ ) {
730
+ isInsideContextValue = true;
731
+ } else if (
732
+ insideContextProps === parent &&
733
+ parentPropertyName === 'children'
734
+ ) {
735
+ isInsideContextValue = false;
736
+ }
737
+ }
738
+ return renderModel(request, task, parent, parentPropertyName, value);
739
+ },
740
thenableState: null,
741
};
742
abortSet.add(task);
816
function serializeClientReference(
817
request: Request,
818
parent:
736
- | {+[key: string | number]: ReactClientValue}
819
+ | {+[propertyName: string | number]: ReactClientValue}
820
| $ReadOnlyArray<ReactClientValue>,
738
- key: string,
821
+ parentPropertyName: string,
822
clientReference: ClientReference<any>,
823
): string {
824
const clientReferenceKey: ClientReferenceKey =
826
const writtenClientReferences = request.writtenClientReferences;
827
const existingId = writtenClientReferences.get(clientReferenceKey);
828
if (existingId !== undefined) {
746
- if (parent[0] === REACT_ELEMENT_TYPE && key === '1') {
829
+ if (parent[0] === REACT_ELEMENT_TYPE && parentPropertyName === '1') {
830
// If we're encoding the "type" of an element, we can refer
831
// to that by a lazy reference instead of directly since React
832
// knows how to deal with lazy values. This lets us suspend
843
const importId = request.nextChunkId++;
844
emitImportChunk(request, importId, clientReferenceMetadata);
845
writtenClientReferences.set(clientReferenceKey, importId);
763
- if (parent[0] === REACT_ELEMENT_TYPE && key === '1') {
846
+ if (parent[0] === REACT_ELEMENT_TYPE && parentPropertyName === '1') {
847
// If we're encoding the "type" of an element, we can refer
848
// to that by a lazy reference instead of directly since React
849
// knows how to deal with lazy values. This lets us suspend
861
}
862
}
863
781
-function outlineModel(request: Request, value: any): number {
864
+function outlineModel(request: Request, value: ReactClientValue): number {
865
request.pendingChunks++;
866
const newTask = createTask(
867
request,
875
876
function serializeServerReference(
877
request: Request,
795
- parent:
796
- | {+[key: string | number]: ReactClientValue}
797
- | $ReadOnlyArray<ReactClientValue>,
798
- key: string,
878
serverReference: ServerReference<any>,
879
): string {
880
const writtenServerReferences = request.writtenServerReferences;
990
let isInsideContextValue = false;
991
let modelRoot: null | ReactClientValue = false;
992
914
-function resolveModelToJSON(
993
+function renderModel(
994
request: Request,
995
+ task: Task,
996
parent:
997
| {+[key: string | number]: ReactClientValue}
998
| $ReadOnlyArray<ReactClientValue>,
999
key: string,
1000
value: ReactClientValue,
1001
): ReactJSONValue {
922
- // Make sure that `parent[key]` wasn't JSONified before `value` was passed to us
923
- if (__DEV__) {
924
- // $FlowFixMe[incompatible-use]
925
- const originalValue = parent[key];
926
- if (
927
- typeof originalValue === 'object' &&
928
- originalValue !== value &&
929
- !(originalValue instanceof Date)
930
- ) {
931
- if (objectName(originalValue) !== 'Object') {
932
- const jsxParentType = jsxChildrenParents.get(parent);
933
- if (typeof jsxParentType === 'string') {
934
- console.error(
935
- '%s objects cannot be rendered as text children. Try formatting it using toString().%s',
936
- objectName(originalValue),
937
- describeObjectForErrorMessage(parent, key),
938
- );
939
- } else {
940
- console.error(
941
- 'Only plain objects can be passed to Client Components from Server Components. ' +
942
- '%s objects are not supported.%s',
943
- objectName(originalValue),
944
- describeObjectForErrorMessage(parent, key),
945
- );
946
- }
947
- } else {
948
- console.error(
949
- 'Only plain objects can be passed to Client Components from Server Components. ' +
950
- 'Objects with toJSON methods are not supported. Convert it manually ' +
951
- 'to a simple value before passing it to props.%s',
952
- describeObjectForErrorMessage(parent, key),
1002
+ try {
1003
+ return renderModelDestructive(request, task, parent, key, value, null);
1004
+ } catch (thrownValue) {
1005
+ const x =
1006
+ thrownValue === SuspenseException
1007
+ ? // This is a special type of exception used for Suspense. For historical
1008
+ // reasons, the rest of the Suspense implementation expects the thrown
1009
+ // value to be a thenable, because before `use` existed that was the
1010
+ // (unstable) API for suspending. This implementation detail can change
1011
+ // later, once we deprecate the old API in favor of `use`.
1012
+ getSuspendedThenable()
1013
+ : thrownValue;
1014
+ // If the suspended/errored value was an element or lazy it can be reduced
1015
+ // to a lazy reference, so that it doesn't error the parent.
1016
+ const model = task.model;
1017
+ const wasReactNode =
1018
+ typeof model === 'object' &&
1019
+ model !== null &&
1020
+ ((model: any).$$typeof === REACT_ELEMENT_TYPE ||
1021
+ (model: any).$$typeof === REACT_LAZY_TYPE);
1022
+ if (typeof x === 'object' && x !== null) {
1023
+ // $FlowFixMe[method-unbinding]
1024
+ if (typeof x.then === 'function') {
1025
+ // Something suspended, we'll need to create a new task and resolve it later.
1026
+ request.pendingChunks++;
1027
+ const newTask = createTask(
1028
+ request,
1029
+ task.model,
1030
+ getActiveContext(),
1031
+ request.abortableTasks,
1032
);
954
- }
955
- }
956
- }
957
-
958
- // Special Symbols
959
- switch (value) {
960
- case REACT_ELEMENT_TYPE:
961
- return '$';
962
- }
963
-
964
- if (__DEV__) {
965
- if (
966
- enableServerContext &&
967
- parent[0] === REACT_ELEMENT_TYPE &&
968
- parent[1] &&
969
- (parent[1]: any).$$typeof === REACT_PROVIDER_TYPE &&
970
- key === '3'
971
- ) {
972
- insideContextProps = value;
973
- } else if (insideContextProps === parent && key === 'value') {
974
- isInsideContextValue = true;
975
- } else if (insideContextProps === parent && key === 'children') {
976
- isInsideContextValue = false;
977
- }
978
- }
979
-
980
- // Resolve Server Components.
981
- while (
982
- typeof value === 'object' &&
983
- value !== null &&
984
- ((value: any).$$typeof === REACT_ELEMENT_TYPE ||
985
- (value: any).$$typeof === REACT_LAZY_TYPE)
986
- ) {
987
- if (__DEV__) {
988
- if (enableServerContext && isInsideContextValue) {
989
- console.error('React elements are not allowed in ServerContext');
990
- }
991
- }
992
-
993
- try {
994
- switch ((value: any).$$typeof) {
995
- case REACT_ELEMENT_TYPE: {
996
- const writtenObjects = request.writtenObjects;
997
- const existingId = writtenObjects.get(value);
998
- if (existingId !== undefined) {
999
- if (existingId === -1) {
1000
- // Seen but not yet outlined.
1001
- const newId = outlineModel(request, value);
1002
- return serializeByValueID(newId);
1003
- } else if (modelRoot === value) {
1004
- // This is the ID we're currently emitting so we need to write it
1005
- // once but if we discover it again, we refer to it by id.
1006
- modelRoot = null;
1007
- } else {
1008
- // We've already emitted this as an outlined object, so we can
1009
- // just refer to that by its existing ID.
1010
- return serializeByValueID(existingId);
1011
- }
1012
- } else {
1013
- // This is the first time we've seen this object. We may never see it again
1014
- // so we'll inline it. Mark it as seen. If we see it again, we'll outline.
1015
- writtenObjects.set(value, -1);
1016
- }
1017
-
1018
- // TODO: Concatenate keys of parents onto children.
1019
- const element: React$Element<any> = (value: any);
1020
- // Attempt to render the Server Component.
1021
- value = attemptResolveElement(
1022
- request,
1023
- element.type,
1024
- element.key,
1025
- element.ref,
1026
- element.props,
1027
- null,
1028
- );
1029
- break;
1030
- }
1031
- case REACT_LAZY_TYPE: {
1032
- const payload = (value: any)._payload;
1033
- const init = (value: any)._init;
1034
- value = init(payload);
1035
- break;
1036
- }
1037
- }
1038
- } catch (thrownValue) {
1039
- const x =
1040
- thrownValue === SuspenseException
1041
- ? // This is a special type of exception used for Suspense. For historical
1042
- // reasons, the rest of the Suspense implementation expects the thrown
1043
- // value to be a thenable, because before `use` existed that was the
1044
- // (unstable) API for suspending. This implementation detail can change
1045
- // later, once we deprecate the old API in favor of `use`.
1046
- getSuspendedThenable()
1047
- : thrownValue;
1048
- if (typeof x === 'object' && x !== null) {
1049
- // $FlowFixMe[method-unbinding]
1050
- if (typeof x.then === 'function') {
1051
- // Something suspended, we'll need to create a new task and resolve it later.
1052
- request.pendingChunks++;
1053
- const newTask = createTask(
1054
- request,
1055
- value,
1056
- getActiveContext(),
1057
- request.abortableTasks,
1058
- );
1059
- const ping = newTask.ping;
1060
- x.then(ping, ping);
1061
- newTask.thenableState = getThenableStateAfterSuspending();
1033
+ const ping = newTask.ping;
1034
+ (x: any).then(ping, ping);
1035
+ newTask.thenableState = getThenableStateAfterSuspending();
1036
+ if (wasReactNode) {
1037
return serializeLazyID(newTask.id);
1063
- } else if (enablePostpone && x.$$typeof === REACT_POSTPONE_TYPE) {
1064
- // Something postponed. We'll still send everything we have up until this point.
1065
- // We'll replace this element with a lazy reference that postpones on the client.
1066
- const postponeInstance: Postpone = (x: any);
1067
- request.pendingChunks++;
1068
- const postponeId = request.nextChunkId++;
1069
- logPostpone(request, postponeInstance.message);
1070
- emitPostponeChunk(request, postponeId, postponeInstance);
1038
+ }
1039
+ return serializeByValueID(newTask.id);
1040
+ } else if (enablePostpone && x.$$typeof === REACT_POSTPONE_TYPE) {
1041
+ // Something postponed. We'll still send everything we have up until this point.
1042
+ // We'll replace this element with a lazy reference that postpones on the client.
1043
+ const postponeInstance: Postpone = (x: any);
1044
+ request.pendingChunks++;
1045
+ const postponeId = request.nextChunkId++;
1046
+ logPostpone(request, postponeInstance.message);
1047
+ emitPostponeChunk(request, postponeId, postponeInstance);
1048
+ if (wasReactNode) {
1049
return serializeLazyID(postponeId);
1050
}
1051
+ return serializeByValueID(postponeId);
1052
}
1053
+ }
1054
+ if (wasReactNode) {
1055
// Something errored. We'll still send everything we have up until this point.
1056
// We'll replace this element with a lazy reference that throws on the client
1057
// once it gets rendered.
1061
emitErrorChunk(request, errorId, digest, x);
1062
return serializeLazyID(errorId);
1063
}
1064
+ // Something errored but it was not in a React Node. There's no need to serialize
1065
+ // it by value because it'll just error the whole parent row anyway so we can
1066
+ // just stop any siblings and error the whole parent row.
1067
+ throw x;
1068
+ }
1069
+}
1070
+
1071
+function renderModelDestructive(
1072
+ request: Request,
1073
+ task: Task,
1074
+ parent:
1075
+ | {+[propertyName: string | number]: ReactClientValue}
1076
+ | $ReadOnlyArray<ReactClientValue>,
1077
+ parentPropertyName: string,
1078
+ value: ReactClientValue,
1079
+ prevThenableState: ThenableState | null,
1080
+): ReactJSONValue {
1081
+ // Set the currently rendering model
1082
+ task.model = value;
1083
+
1084
+ // Special Symbol, that's very common.
1085
+ if (value === REACT_ELEMENT_TYPE) {
1086
+ return '$';
1087
}
1088
1089
if (value === null) {
1091
}
1092
1093
if (typeof value === 'object') {
1094
+ switch ((value: any).$$typeof) {
1095
+ case REACT_ELEMENT_TYPE: {
1096
+ if (__DEV__) {
1097
+ if (enableServerContext && isInsideContextValue) {
1098
+ console.error('React elements are not allowed in ServerContext');
1099
+ }
1100
+ }
1101
+ const writtenObjects = request.writtenObjects;
1102
+ const existingId = writtenObjects.get(value);
1103
+ if (existingId !== undefined) {
1104
+ if (existingId === -1) {
1105
+ // Seen but not yet outlined.
1106
+ const newId = outlineModel(request, value);
1107
+ return serializeByValueID(newId);
1108
+ } else if (modelRoot === value) {
1109
+ // This is the ID we're currently emitting so we need to write it
1110
+ // once but if we discover it again, we refer to it by id.
1111
+ modelRoot = null;
1112
+ } else {
1113
+ // We've already emitted this as an outlined object, so we can
1114
+ // just refer to that by its existing ID.
1115
+ return serializeByValueID(existingId);
1116
+ }
1117
+ } else {
1118
+ // This is the first time we've seen this object. We may never see it again
1119
+ // so we'll inline it. Mark it as seen. If we see it again, we'll outline.
1120
+ writtenObjects.set(value, -1);
1121
+ }
1122
+
1123
+ // TODO: Concatenate keys of parents onto children.
1124
+ const element: React$Element<any> = (value: any);
1125
+ // Attempt to render the Server Component.
1126
+ return renderElement(
1127
+ request,
1128
+ task,
1129
+ element.type,
1130
+ element.key,
1131
+ element.ref,
1132
+ element.props,
1133
+ prevThenableState,
1134
+ );
1135
+ }
1136
+ case REACT_LAZY_TYPE: {
1137
+ const payload = (value: any)._payload;
1138
+ const init = (value: any)._init;
1139
+ const resolvedModel = init(payload);
1140
+ return renderModelDestructive(
1141
+ request,
1142
+ task,
1143
+ emptyRoot,
1144
+ '',
1145
+ resolvedModel,
1146
+ null,
1147
+ );
1148
+ }
1149
+ }
1150
+
1151
+ if (isClientReference(value)) {
1152
+ return serializeClientReference(
1153
+ request,
1154
+ parent,
1155
+ parentPropertyName,
1156
+ (value: any),
1157
+ );
1158
+ }
1159
+
1160
if (enableTaint) {
1161
const tainted = TaintRegistryObjects.get(value);
1162
if (tainted !== undefined) {
1163
throwTaintViolation(tainted);
1164
}
1165
}
1096
- if (isClientReference(value)) {
1097
- return serializeClientReference(request, parent, key, (value: any));
1098
- }
1166
1167
const writtenObjects = request.writtenObjects;
1168
const existingId = writtenObjects.get(value);
1190
const providerKey = ((value: any): ReactProviderType<any>)._context
1191
._globalName;
1192
const writtenProviders = request.writtenProviders;
1126
- let providerId = writtenProviders.get(key);
1193
+ let providerId = writtenProviders.get(providerKey);
1194
if (providerId === undefined) {
1195
request.pendingChunks++;
1196
providerId = request.nextChunkId++;
1199
}
1200
return serializeByValueID(providerId);
1201
} else if (value === POP) {
1135
- popProvider();
1202
+ task.context = popProvider();
1203
if (__DEV__) {
1204
insideContextProps = null;
1205
isInsideContextValue = false;
1316
'Only plain objects can be passed to Client Components from Server Components. ' +
1317
'%s objects are not supported.%s',
1318
objectName(value),
1252
- describeObjectForErrorMessage(parent, key),
1319
+ describeObjectForErrorMessage(parent, parentPropertyName),
1320
);
1321
} else if (!isSimpleObject(value)) {
1322
console.error(
1323
'Only plain objects can be passed to Client Components from Server Components. ' +
1324
'Classes or other objects with methods are not supported.%s',
1258
- describeObjectForErrorMessage(parent, key),
1325
+ describeObjectForErrorMessage(parent, parentPropertyName),
1326
);
1327
} else if (Object.getOwnPropertySymbols) {
1328
const symbols = Object.getOwnPropertySymbols(value);
1331
'Only plain objects can be passed to Client Components from Server Components. ' +
1332
'Objects with symbol properties like %s are not supported.%s',
1333
symbols[0].description,
1267
- describeObjectForErrorMessage(parent, key),
1334
+ describeObjectForErrorMessage(parent, parentPropertyName),
1335
);
1336
}
1337
}
1352
if (value[value.length - 1] === 'Z') {
1353
// Possibly a Date, whose toJSON automatically calls toISOString
1354
// $FlowFixMe[incompatible-use]
1288
- const originalValue = parent[key];
1355
+ const originalValue = parent[parentPropertyName];
1356
if (originalValue instanceof Date) {
1357
return serializeDateFromDateJSON(value);
1358
}
1379
}
1380
1381
if (typeof value === 'function') {
1382
+ if (isClientReference(value)) {
1383
+ return serializeClientReference(
1384
+ request,
1385
+ parent,
1386
+ parentPropertyName,
1387
+ (value: any),
1388
+ );
1389
+ }
1390
+ if (isServerReference(value)) {
1391
+ return serializeServerReference(request, (value: any));
1392
+ }
1393
+
1394
if (enableTaint) {
1395
const tainted = TaintRegistryObjects.get(value);
1396
if (tainted !== undefined) {
1397
throwTaintViolation(tainted);
1398
}
1399
}
1321
- if (isClientReference(value)) {
1322
- return serializeClientReference(request, parent, key, (value: any));
1323
- }
1324
- if (isServerReference(value)) {
1325
- return serializeServerReference(request, parent, key, (value: any));
1326
- }
1327
- if (/^on[A-Z]/.test(key)) {
1400
+
1401
+ if (/^on[A-Z]/.test(parentPropertyName)) {
1402
throw new Error(
1403
'Event handlers cannot be passed to Client Component props.' +
1330
- describeObjectForErrorMessage(parent, key) +
1404
+ describeObjectForErrorMessage(parent, parentPropertyName) +
1405
'\nIf you need interactivity, consider converting part of this to a Client Component.',
1406
);
1407
} else {
1408
throw new Error(
1409
'Functions cannot be passed directly to Client Components ' +
1410
'unless you explicitly expose it by marking it with "use server".' +
1337
- describeObjectForErrorMessage(parent, key),
1411
+ describeObjectForErrorMessage(parent, parentPropertyName),
1412
);
1413
}
1414
}
1429
// $FlowFixMe[incompatible-type] `description` might be undefined
1430
value.description
1431
}) cannot be found among global symbols.` +
1358
- describeObjectForErrorMessage(parent, key),
1432
+ describeObjectForErrorMessage(parent, parentPropertyName),
1433
);
1434
}
1435
1452
1453
throw new Error(
1454
`Type ${typeof value} is not supported in Client Component props.` +
1381
- describeObjectForErrorMessage(parent, key),
1455
+ describeObjectForErrorMessage(parent, parentPropertyName),
1456
);
1457
}
1458
1582
request.completedRegularChunks.push(processedChunk);
1583
}
1584
1511
-function emitModelChunk(
1512
- request: Request,
1513
- id: number,
1514
- model: ReactClientValue,
1515
-): void {
1516
- // Track the root so we know that we have to emit this object even though it
1517
- // already has an ID. This is needed because we might see this object twice
1518
- // in the same toJSON if it is cyclic.
1519
- modelRoot = model;
1520
- // $FlowFixMe[incompatible-type] stringify can return null
1521
- const json: string = stringify(model, request.toJSON);
1585
+function emitModelChunk(request: Request, id: number, json: string): void {
1586
const row = id.toString(16) + ':' + json + '\n';
1587
const processedChunk = stringToChunk(row);
1588
request.completedRegularChunks.push(processedChunk);
1589
}
1590
1591
+const emptyRoot = {};
1592
+
1593
function retryTask(request: Request, task: Task): void {
1594
if (task.status !== PENDING) {
1595
// We completed this by other means before we had a chance to retry it.
1598
1599
switchContext(task.context);
1600
try {
1535
- let value = task.model;
1536
- if (
1537
- typeof value === 'object' &&
1538
- value !== null &&
1539
- (value: any).$$typeof === REACT_ELEMENT_TYPE
1540
- ) {
1541
- request.writtenObjects.set(value, task.id);
1542
-
1543
- // TODO: Concatenate keys of parents onto children.
1544
- const element: React$Element<any> = (value: any);
1545
-
1546
- // When retrying a component, reuse the thenableState from the
1547
- // previous attempt.
1548
- const prevThenableState = task.thenableState;
1549
-
1550
- // Attempt to render the Server Component.
1551
- // Doing this here lets us reuse this same task if the next component
1552
- // also suspends.
1553
- task.model = value;
1554
- value = attemptResolveElement(
1555
- request,
1556
- element.type,
1557
- element.key,
1558
- element.ref,
1559
- element.props,
1560
- prevThenableState,
1561
- );
1601
+ // Reset the task's thenable state before continuing, so that if a later
1602
+ // component suspends we can reuse the same task object. If the same
1603
+ // component suspends again, the thenable state will be restored.
1604
+ const prevThenableState = task.thenableState;
1605
+ task.thenableState = null;
1606
+
1607
+ // Track the root so we know that we have to emit this object even though it
1608
+ // already has an ID. This is needed because we might see this object twice
1609
+ // in the same toJSON if it is cyclic.
1610
+ modelRoot = task.model;
1611
+
1612
+ // We call the destructive form that mutates this task. That way if something
1613
+ // suspends again, we can reuse the same task instead of spawning a new one.
1614
+ const resolvedModel = renderModelDestructive(
1615
+ request,
1616
+ task,
1617
+ emptyRoot,
1618
+ '',
1619
+ task.model,
1620
+ prevThenableState,
1621
+ );
1622
1563
- // Successfully finished this component. We're going to keep rendering
1564
- // using the same task, but we reset its thenable state before continuing.
1565
- task.thenableState = null;
1566
-
1567
- // Keep rendering and reuse the same task. This inner loop is separate
1568
- // from the render above because we don't need to reset the thenable state
1569
- // until the next time something suspends and retries.
1570
- while (
1571
- typeof value === 'object' &&
1572
- value !== null &&
1573
- (value: any).$$typeof === REACT_ELEMENT_TYPE
1574
- ) {
1575
- request.writtenObjects.set(value, task.id);
1576
- // TODO: Concatenate keys of parents onto children.
1577
- const nextElement: React$Element<any> = (value: any);
1578
- task.model = value;
1579
- value = attemptResolveElement(
1580
- request,
1581
- nextElement.type,
1582
- nextElement.key,
1583
- nextElement.ref,
1584
- nextElement.props,
1585
- null,
1586
- );
1587
- }
1588
- }
1623
+ // Track the root again for the resolved object.
1624
+ modelRoot = resolvedModel;
1625
1590
- // Track that this object is outlined and has an id.
1591
- if (typeof value === 'object' && value !== null) {
1592
- request.writtenObjects.set(value, task.id);
1593
- }
1626
+ // If the value is a string, it means it's a terminal value adn we already escaped it
1627
+ // We don't need to escape it again so it's not passed the toJSON replacer.
1628
+ // Object might contain unresolved values like additional elements.
1629
+ // This is simulating what the JSON loop would do if this was part of it.
1630
+ // $FlowFixMe[incompatible-type] stringify can return null
1631
+ const json: string =
1632
+ typeof resolvedModel === 'string'
1633
+ ? stringify(resolvedModel)
1634
+ : stringify(resolvedModel, task.toJSON);
1635
+ emitModelChunk(request, task.id, json);
1636
1595
- emitModelChunk(request, task.id, value);
1637
request.abortableTasks.delete(task);
1638
task.status = COMPLETED;
1639
} catch (thrownValue) {