Skip to content

Commit 7a01513

Browse files
committed
[Flight] Unify plain Server Component and forwardRef under one function (#28261)
This used to be trivial but it's no longer trivial. In Fizz and Fiber this is split into renderWithHooks and finishFunctionComponent since they also support indeterminate components. Interestingly thanks to this unification we always call functions with an arity of 2 which is a bit weird - with the second argument being undefined in everything except forwardRef and legacy context consumers. This makes Flight makes the same thing but we could also call it with an arity of 1. Since Flight errors early if you try to pass it a ref, and there's no legacy context, the second arg is always undefined. The practical change in this PR is that returning a Promise from a forwardRef now turns it into a lazy. We previously didn't support async forwardRef since it wasn't supported on the client. However, since eventually this will be supported by child-as-a-promise it seems fine to support it. DiffTrain build for [f07ac1e](f07ac1e)
1 parent de5b760 commit 7a01513

File tree

5 files changed

+98
-137
lines changed

5 files changed

+98
-137
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1beb94133a93a433669a893aef02dd5afec07394
1+
f07ac1e2680a26c5b3bf9c651d62c792de71d46d

compiled/facebook-www/ReactDOMTesting-prod.modern.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17067,7 +17067,7 @@ Internals.Events = [
1706717067
var devToolsConfig$jscomp$inline_1787 = {
1706817068
findFiberByHostInstance: getClosestInstanceFromNode,
1706917069
bundleType: 0,
17070-
version: "18.3.0-www-modern-941395b7",
17070+
version: "18.3.0-www-modern-55799591",
1707117071
rendererPackageName: "react-dom"
1707217072
};
1707317073
var internals$jscomp$inline_2160 = {
@@ -17098,7 +17098,7 @@ var internals$jscomp$inline_2160 = {
1709817098
scheduleRoot: null,
1709917099
setRefreshHandler: null,
1710017100
getCurrentFiber: null,
17101-
reconcilerVersion: "18.3.0-www-modern-941395b7"
17101+
reconcilerVersion: "18.3.0-www-modern-55799591"
1710217102
};
1710317103
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
1710417104
var hook$jscomp$inline_2161 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -17526,4 +17526,4 @@ exports.useFormStatus = function () {
1752617526
return ReactCurrentDispatcher$2.current.useHostTransitionStatus();
1752717527
throw Error(formatProdErrorMessage(248));
1752817528
};
17529-
exports.version = "18.3.0-www-modern-941395b7";
17529+
exports.version = "18.3.0-www-modern-55799591";

compiled/facebook-www/ReactFlightDOMServer-dev.modern.js

Lines changed: 58 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,55 @@ if (__DEV__) {
13081308
return lazyType;
13091309
}
13101310

1311+
function renderFunctionComponent(request, task, key, Component, props) {
1312+
// Reset the task's thenable state before continuing, so that if a later
1313+
// component suspends we can reuse the same task object. If the same
1314+
// component suspends again, the thenable state will be restored.
1315+
var prevThenableState = task.thenableState;
1316+
task.thenableState = null;
1317+
prepareToUseHooksForComponent(prevThenableState); // The secondArg is always undefined in Server Components since refs error early.
1318+
1319+
var secondArg = undefined;
1320+
var result = Component(props, secondArg);
1321+
1322+
if (
1323+
typeof result === "object" &&
1324+
result !== null &&
1325+
typeof result.then === "function"
1326+
) {
1327+
// When the return value is in children position we can resolve it immediately,
1328+
// to its value without a wrapper if it's synchronously available.
1329+
var thenable = result;
1330+
1331+
if (thenable.status === "fulfilled") {
1332+
return thenable.value;
1333+
} // TODO: Once we accept Promises as children on the client, we can just return
1334+
// the thenable here.
1335+
1336+
result = createLazyWrapperAroundWakeable(result);
1337+
} // Track this element's key on the Server Component on the keyPath context..
1338+
1339+
var prevKeyPath = task.keyPath;
1340+
var prevImplicitSlot = task.implicitSlot;
1341+
1342+
if (key !== null) {
1343+
// Append the key to the path. Technically a null key should really add the child
1344+
// index. We don't do that to hold the payload small and implementation simple.
1345+
task.keyPath = prevKeyPath === null ? key : prevKeyPath + "," + key;
1346+
} else if (prevKeyPath === null) {
1347+
// This sequence of Server Components has no keys. This means that it was rendered
1348+
// in a slot that needs to assign an implicit key. Even if children below have
1349+
// explicit keys, they should not be used for the outer most key since it might
1350+
// collide with other slots in that set.
1351+
task.implicitSlot = true;
1352+
}
1353+
1354+
var json = renderModelDestructive(request, task, emptyRoot, "", result);
1355+
task.keyPath = prevKeyPath;
1356+
task.implicitSlot = prevImplicitSlot;
1357+
return json;
1358+
}
1359+
13111360
function renderFragment(request, task, children) {
13121361
if (task.keyPath !== null) {
13131362
// We have a Server Component that specifies a key but we're now splitting
@@ -1397,74 +1446,30 @@ if (__DEV__) {
13971446
// This is a reference to a Client Component.
13981447
return renderClientElement(task, type, key, props);
13991448
} // This is a server-side component.
1400-
// Reset the task's thenable state before continuing, so that if a later
1401-
// component suspends we can reuse the same task object. If the same
1402-
// component suspends again, the thenable state will be restored.
1403-
1404-
var prevThenableState = task.thenableState;
1405-
task.thenableState = null;
1406-
prepareToUseHooksForComponent(prevThenableState);
1407-
var result = type(props);
14081449

1409-
if (
1410-
typeof result === "object" &&
1411-
result !== null &&
1412-
typeof result.then === "function"
1413-
) {
1414-
// When the return value is in children position we can resolve it immediately,
1415-
// to its value without a wrapper if it's synchronously available.
1416-
var thenable = result;
1417-
1418-
if (thenable.status === "fulfilled") {
1419-
return thenable.value;
1420-
} // TODO: Once we accept Promises as children on the client, we can just return
1421-
// the thenable here.
1422-
1423-
result = createLazyWrapperAroundWakeable(result);
1424-
} // Track this element's key on the Server Component on the keyPath context..
1425-
1426-
var prevKeyPath = task.keyPath;
1427-
var prevImplicitSlot = task.implicitSlot;
1428-
1429-
if (key !== null) {
1430-
// Append the key to the path. Technically a null key should really add the child
1431-
// index. We don't do that to hold the payload small and implementation simple.
1432-
task.keyPath = prevKeyPath === null ? key : prevKeyPath + "," + key;
1433-
} else if (prevKeyPath === null) {
1434-
// This sequence of Server Components has no keys. This means that it was rendered
1435-
// in a slot that needs to assign an implicit key. Even if children below have
1436-
// explicit keys, they should not be used for the outer most key since it might
1437-
// collide with other slots in that set.
1438-
task.implicitSlot = true;
1439-
}
1440-
1441-
var json = renderModelDestructive(request, task, emptyRoot, "", result);
1442-
task.keyPath = prevKeyPath;
1443-
task.implicitSlot = prevImplicitSlot;
1444-
return json;
1450+
return renderFunctionComponent(request, task, key, type, props);
14451451
} else if (typeof type === "string") {
14461452
// This is a host element. E.g. HTML.
14471453
return renderClientElement(task, type, key, props);
14481454
} else if (typeof type === "symbol") {
14491455
if (type === REACT_FRAGMENT_TYPE && key === null) {
14501456
// For key-less fragments, we add a small optimization to avoid serializing
14511457
// it as a wrapper.
1452-
var _prevImplicitSlot = task.implicitSlot;
1458+
var prevImplicitSlot = task.implicitSlot;
14531459

14541460
if (task.keyPath === null) {
14551461
task.implicitSlot = true;
14561462
}
14571463

1458-
var _json = renderModelDestructive(
1464+
var json = renderModelDestructive(
14591465
request,
14601466
task,
14611467
emptyRoot,
14621468
"",
14631469
props.children
14641470
);
1465-
1466-
task.implicitSlot = _prevImplicitSlot;
1467-
return _json;
1471+
task.implicitSlot = prevImplicitSlot;
1472+
return json;
14681473
} // This might be a built-in React component. We'll let the client decide.
14691474
// Any built-in works as long as its props are serializable.
14701475

@@ -1484,43 +1489,13 @@ if (__DEV__) {
14841489
}
14851490

14861491
case REACT_FORWARD_REF_TYPE: {
1487-
var render = type.render; // Reset the task's thenable state before continuing, so that if a later
1488-
// component suspends we can reuse the same task object. If the same
1489-
// component suspends again, the thenable state will be restored.
1490-
1491-
var _prevThenableState = task.thenableState;
1492-
task.thenableState = null;
1493-
prepareToUseHooksForComponent(_prevThenableState);
1494-
1495-
var _result = render(props, undefined);
1496-
1497-
var _prevKeyPath = task.keyPath;
1498-
var _prevImplicitSlot2 = task.implicitSlot;
1499-
1500-
if (key !== null) {
1501-
// Append the key to the path. Technically a null key should really add the child
1502-
// index. We don't do that to hold the payload small and implementation simple.
1503-
task.keyPath =
1504-
_prevKeyPath === null ? key : _prevKeyPath + "," + key;
1505-
} else if (_prevKeyPath === null) {
1506-
// This sequence of Server Components has no keys. This means that it was rendered
1507-
// in a slot that needs to assign an implicit key. Even if children below have
1508-
// explicit keys, they should not be used for the outer most key since it might
1509-
// collide with other slots in that set.
1510-
task.implicitSlot = true;
1511-
}
1512-
1513-
var _json2 = renderModelDestructive(
1492+
return renderFunctionComponent(
15141493
request,
15151494
task,
1516-
emptyRoot,
1517-
"",
1518-
_result
1495+
key,
1496+
type.render,
1497+
props
15191498
);
1520-
1521-
task.keyPath = _prevKeyPath;
1522-
task.implicitSlot = _prevImplicitSlot2;
1523-
return _json2;
15241499
}
15251500

15261501
case REACT_MEMO_TYPE: {

compiled/facebook-www/ReactFlightDOMServer-prod.modern.js

Lines changed: 35 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,31 @@ function createLazyWrapperAroundWakeable(wakeable) {
539539
}
540540
return { $$typeof: REACT_LAZY_TYPE, _payload: wakeable, _init: readThenable };
541541
}
542+
function renderFunctionComponent(request, task, key, Component, props) {
543+
var prevThenableState = task.thenableState;
544+
task.thenableState = null;
545+
thenableIndexCounter = 0;
546+
thenableState = prevThenableState;
547+
Component = Component(props, void 0);
548+
if (
549+
"object" === typeof Component &&
550+
null !== Component &&
551+
"function" === typeof Component.then
552+
) {
553+
props = Component;
554+
if ("fulfilled" === props.status) return props.value;
555+
Component = createLazyWrapperAroundWakeable(Component);
556+
}
557+
props = task.keyPath;
558+
prevThenableState = task.implicitSlot;
559+
null !== key
560+
? (task.keyPath = null === props ? key : props + "," + key)
561+
: null === props && (task.implicitSlot = !0);
562+
request = renderModelDestructive(request, task, emptyRoot, "", Component);
563+
task.keyPath = props;
564+
task.implicitSlot = prevThenableState;
565+
return request;
566+
}
542567
function renderFragment(request, task, children) {
543568
return null !== task.keyPath
544569
? ((request = [
@@ -563,33 +588,10 @@ function renderElement(request, task, type, key, ref, props) {
563588
throw Error(
564589
"Refs cannot be used in Server Components, nor passed to Client Components."
565590
);
566-
if ("function" === typeof type) {
567-
if (isClientReference(type))
568-
return renderClientElement(task, type, key, props);
569-
ref = task.thenableState;
570-
task.thenableState = null;
571-
thenableIndexCounter = 0;
572-
thenableState = ref;
573-
props = type(props);
574-
if (
575-
"object" === typeof props &&
576-
null !== props &&
577-
"function" === typeof props.then
578-
) {
579-
type = props;
580-
if ("fulfilled" === type.status) return type.value;
581-
props = createLazyWrapperAroundWakeable(props);
582-
}
583-
type = task.keyPath;
584-
ref = task.implicitSlot;
585-
null !== key
586-
? (task.keyPath = null === type ? key : type + "," + key)
587-
: null === type && (task.implicitSlot = !0);
588-
request = renderModelDestructive(request, task, emptyRoot, "", props);
589-
task.keyPath = type;
590-
task.implicitSlot = ref;
591-
return request;
592-
}
591+
if ("function" === typeof type)
592+
return isClientReference(type)
593+
? renderClientElement(task, type, key, props)
594+
: renderFunctionComponent(request, task, key, type, props);
593595
if ("string" === typeof type)
594596
return renderClientElement(task, type, key, props);
595597
if ("symbol" === typeof type)
@@ -615,23 +617,7 @@ function renderElement(request, task, type, key, ref, props) {
615617
type = init(type._payload);
616618
return renderElement(request, task, type, key, ref, props);
617619
case REACT_FORWARD_REF_TYPE:
618-
return (
619-
(type = type.render),
620-
(ref = task.thenableState),
621-
(task.thenableState = null),
622-
(thenableIndexCounter = 0),
623-
(thenableState = ref),
624-
(ref = type(props, void 0)),
625-
(props = task.keyPath),
626-
(type = task.implicitSlot),
627-
null !== key
628-
? (task.keyPath = null === props ? key : props + "," + key)
629-
: null === props && (task.implicitSlot = !0),
630-
(request = renderModelDestructive(request, task, emptyRoot, "", ref)),
631-
(task.keyPath = props),
632-
(task.implicitSlot = type),
633-
request
634-
);
620+
return renderFunctionComponent(request, task, key, type.render, props);
635621
case REACT_MEMO_TYPE:
636622
return renderElement(request, task, type.type, key, ref, props);
637623
}
@@ -928,18 +914,18 @@ function renderModelDestructive(
928914
}
929915
if ("symbol" === typeof value) {
930916
task = request.writtenSymbols;
931-
var existingId$15 = task.get(value);
932-
if (void 0 !== existingId$15) return serializeByValueID(existingId$15);
933-
existingId$15 = value.description;
934-
if (Symbol.for(existingId$15) !== value)
917+
var existingId$8 = task.get(value);
918+
if (void 0 !== existingId$8) return serializeByValueID(existingId$8);
919+
existingId$8 = value.description;
920+
if (Symbol.for(existingId$8) !== value)
935921
throw Error(
936922
"Only global symbols received from Symbol.for(...) can be passed to Client Components. The symbol Symbol.for(" +
937923
(value.description + ") cannot be found among global symbols.") +
938924
describeObjectForErrorMessage(parent, parentPropertyName)
939925
);
940926
request.pendingChunks++;
941927
parent = request.nextChunkId++;
942-
parentPropertyName = stringify("$S" + existingId$15);
928+
parentPropertyName = stringify("$S" + existingId$8);
943929
parentPropertyName = parent.toString(16) + ":" + parentPropertyName + "\n";
944930
request.completedImportChunks.push(parentPropertyName);
945931
task.set(value, parent);

compiled/facebook-www/ReactTestRenderer-dev.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26083,7 +26083,7 @@ if (__DEV__) {
2608326083
return root;
2608426084
}
2608526085

26086-
var ReactVersion = "18.3.0-www-modern-6f034660";
26086+
var ReactVersion = "18.3.0-www-modern-c1410230";
2608726087

2608826088
// Might add PROFILE later.
2608926089

0 commit comments

Comments
 (0)