Skip to content

Commit e6ea24b

Browse files
committed
Move string ref coercion to JSX runtime (#28473)
Based on: - #28464 --- This moves the entire string ref implementation out Fiber and into the JSX runtime. The string is converted to a callback ref during element creation. This is a subtle change in behavior, because it will have already been converted to a callback ref if you access element.prop.ref or element.ref. But this is only for Meta, because string refs are disabled entirely in open source. And if it leads to an issue in practice, the solution is to switch to a different ref type, which Meta is going to do regardless. DiffTrain build for [e3ebcd5](e3ebcd5)
1 parent 769be69 commit e6ea24b

27 files changed

+2022
-1972
lines changed

compiled/facebook-www/JSXDEVRuntime-dev.classic.js

Lines changed: 279 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,11 @@ if (__DEV__) {
112112
enableRefAsProp = dynamicFeatureFlags.enableRefAsProp,
113113
disableDefaultPropsExceptForClasses =
114114
dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, false is used for a new modern build.
115+
// because JSX is an extremely hot path.
115116

116-
function getWrappedName(outerType, innerType, wrapperName) {
117+
var disableStringRefs = false;
118+
119+
function getWrappedName$1(outerType, innerType, wrapperName) {
117120
var displayName = outerType.displayName;
118121

119122
if (displayName) {
@@ -126,7 +129,7 @@ if (__DEV__) {
126129
: wrapperName;
127130
} // Keep in sync with react-reconciler/getComponentNameFromFiber
128131

129-
function getContextName(type) {
132+
function getContextName$1(type) {
130133
return type.displayName || "Context";
131134
}
132135

@@ -193,28 +196,28 @@ if (__DEV__) {
193196
return null;
194197
} else {
195198
var provider = type;
196-
return getContextName(provider._context) + ".Provider";
199+
return getContextName$1(provider._context) + ".Provider";
197200
}
198201

199202
case REACT_CONTEXT_TYPE:
200203
var context = type;
201204

202205
if (enableRenderableContext) {
203-
return getContextName(context) + ".Provider";
206+
return getContextName$1(context) + ".Provider";
204207
} else {
205-
return getContextName(context) + ".Consumer";
208+
return getContextName$1(context) + ".Consumer";
206209
}
207210

208211
case REACT_CONSUMER_TYPE:
209212
if (enableRenderableContext) {
210213
var consumer = type;
211-
return getContextName(consumer._context) + ".Consumer";
214+
return getContextName$1(consumer._context) + ".Consumer";
212215
} else {
213216
return null;
214217
}
215218

216219
case REACT_FORWARD_REF_TYPE:
217-
return getWrappedName(type, type.render, "ForwardRef");
220+
return getWrappedName$1(type, type.render, "ForwardRef");
218221

219222
case REACT_MEMO_TYPE:
220223
var outerName = type.displayName || null;
@@ -323,6 +326,20 @@ if (__DEV__) {
323326
}
324327
}
325328
}
329+
function checkPropStringCoercion(value, propName) {
330+
{
331+
if (willCoercionThrow(value)) {
332+
error(
333+
"The provided `%s` prop is an unsupported type %s." +
334+
" This value must be coerced to a string before using it here.",
335+
propName,
336+
typeName(value)
337+
);
338+
339+
return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
340+
}
341+
}
342+
}
326343

327344
var REACT_CLIENT_REFERENCE$1 = Symbol.for("react.client.reference");
328345
function isValidElementType(type) {
@@ -802,6 +819,158 @@ if (__DEV__) {
802819
return "";
803820
}
804821

822+
var FunctionComponent = 0;
823+
var ClassComponent = 1;
824+
var HostRoot = 3; // Root of a host tree. Could be nested inside another node.
825+
826+
var HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
827+
828+
var HostComponent = 5;
829+
var HostText = 6;
830+
var Fragment = 7;
831+
var Mode = 8;
832+
var ContextConsumer = 9;
833+
var ContextProvider = 10;
834+
var ForwardRef = 11;
835+
var Profiler = 12;
836+
var SuspenseComponent = 13;
837+
var MemoComponent = 14;
838+
var SimpleMemoComponent = 15;
839+
var LazyComponent = 16;
840+
var IncompleteClassComponent = 17;
841+
var DehydratedFragment = 18;
842+
var SuspenseListComponent = 19;
843+
var ScopeComponent = 21;
844+
var OffscreenComponent = 22;
845+
var LegacyHiddenComponent = 23;
846+
var CacheComponent = 24;
847+
var TracingMarkerComponent = 25;
848+
var HostHoistable = 26;
849+
var HostSingleton = 27;
850+
var IncompleteFunctionComponent = 28;
851+
852+
function getWrappedName(outerType, innerType, wrapperName) {
853+
var functionName = innerType.displayName || innerType.name || "";
854+
return (
855+
outerType.displayName ||
856+
(functionName !== ""
857+
? wrapperName + "(" + functionName + ")"
858+
: wrapperName)
859+
);
860+
} // Keep in sync with shared/getComponentNameFromType
861+
862+
function getContextName(type) {
863+
return type.displayName || "Context";
864+
}
865+
866+
function getComponentNameFromFiber(fiber) {
867+
var tag = fiber.tag,
868+
type = fiber.type;
869+
870+
switch (tag) {
871+
case CacheComponent:
872+
return "Cache";
873+
874+
case ContextConsumer:
875+
if (enableRenderableContext) {
876+
var consumer = type;
877+
return getContextName(consumer._context) + ".Consumer";
878+
} else {
879+
var context = type;
880+
return getContextName(context) + ".Consumer";
881+
}
882+
883+
case ContextProvider:
884+
if (enableRenderableContext) {
885+
var _context = type;
886+
return getContextName(_context) + ".Provider";
887+
} else {
888+
var provider = type;
889+
return getContextName(provider._context) + ".Provider";
890+
}
891+
892+
case DehydratedFragment:
893+
return "DehydratedFragment";
894+
895+
case ForwardRef:
896+
return getWrappedName(type, type.render, "ForwardRef");
897+
898+
case Fragment:
899+
return "Fragment";
900+
901+
case HostHoistable:
902+
case HostSingleton:
903+
case HostComponent:
904+
// Host component type is the display name (e.g. "div", "View")
905+
return type;
906+
907+
case HostPortal:
908+
return "Portal";
909+
910+
case HostRoot:
911+
return "Root";
912+
913+
case HostText:
914+
return "Text";
915+
916+
case LazyComponent:
917+
// Name comes from the type in this case; we don't have a tag.
918+
return getComponentNameFromType(type);
919+
920+
case Mode:
921+
if (type === REACT_STRICT_MODE_TYPE) {
922+
// Don't be less specific than shared/getComponentNameFromType
923+
return "StrictMode";
924+
}
925+
926+
return "Mode";
927+
928+
case OffscreenComponent:
929+
return "Offscreen";
930+
931+
case Profiler:
932+
return "Profiler";
933+
934+
case ScopeComponent:
935+
return "Scope";
936+
937+
case SuspenseComponent:
938+
return "Suspense";
939+
940+
case SuspenseListComponent:
941+
return "SuspenseList";
942+
943+
case TracingMarkerComponent:
944+
return "TracingMarker";
945+
// The display name for these tags come from the user-provided type:
946+
947+
case IncompleteClassComponent:
948+
case IncompleteFunctionComponent:
949+
950+
// Fallthrough
951+
952+
case ClassComponent:
953+
case FunctionComponent:
954+
case MemoComponent:
955+
case SimpleMemoComponent:
956+
if (typeof type === "function") {
957+
return type.displayName || type.name || null;
958+
}
959+
960+
if (typeof type === "string") {
961+
return type;
962+
}
963+
964+
break;
965+
966+
case LegacyHiddenComponent: {
967+
return "LegacyHidden";
968+
}
969+
}
970+
971+
return null;
972+
}
973+
805974
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
806975
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
807976
var REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference");
@@ -1217,6 +1386,10 @@ if (__DEV__) {
12171386
if (hasValidRef(config)) {
12181387
if (!enableRefAsProp) {
12191388
ref = config.ref;
1389+
1390+
{
1391+
ref = coerceStringRef(ref, ReactCurrentOwner.current, type);
1392+
}
12201393
}
12211394

12221395
{
@@ -1230,7 +1403,15 @@ if (__DEV__) {
12301403
propName !== "key" &&
12311404
(enableRefAsProp || propName !== "ref")
12321405
) {
1233-
props[propName] = config[propName];
1406+
if (enableRefAsProp && !disableStringRefs && propName === "ref") {
1407+
props.ref = coerceStringRef(
1408+
config[propName],
1409+
ReactCurrentOwner.current,
1410+
type
1411+
);
1412+
} else {
1413+
props[propName] = config[propName];
1414+
}
12341415
}
12351416
}
12361417

@@ -1488,6 +1669,96 @@ if (__DEV__) {
14881669
}
14891670
}
14901671

1672+
function coerceStringRef(mixedRef, owner, type) {
1673+
var stringRef;
1674+
1675+
if (typeof mixedRef === "string") {
1676+
stringRef = mixedRef;
1677+
} else {
1678+
if (typeof mixedRef === "number" || typeof mixedRef === "boolean") {
1679+
{
1680+
checkPropStringCoercion(mixedRef, "ref");
1681+
}
1682+
1683+
stringRef = "" + mixedRef;
1684+
} else {
1685+
return mixedRef;
1686+
}
1687+
}
1688+
1689+
return stringRefAsCallbackRef.bind(null, stringRef, type, owner);
1690+
}
1691+
1692+
function stringRefAsCallbackRef(stringRef, type, owner, value) {
1693+
if (!owner) {
1694+
throw new Error(
1695+
"Element ref was specified as a string (" +
1696+
stringRef +
1697+
") but no owner was set. This could happen for one of" +
1698+
" the following reasons:\n" +
1699+
"1. You may be adding a ref to a function component\n" +
1700+
"2. You may be adding a ref to a component that was not created inside a component's render method\n" +
1701+
"3. You have multiple copies of React loaded\n" +
1702+
"See https://react.dev/link/refs-must-have-owner for more information."
1703+
);
1704+
}
1705+
1706+
if (owner.tag !== ClassComponent) {
1707+
throw new Error(
1708+
"Function components cannot have string refs. " +
1709+
"We recommend using useRef() instead. " +
1710+
"Learn more about using refs safely here: " +
1711+
"https://react.dev/link/strict-mode-string-ref"
1712+
);
1713+
}
1714+
1715+
{
1716+
if (
1717+
// Will already warn with "Function components cannot be given refs"
1718+
!(typeof type === "function" && !isReactClass(type))
1719+
) {
1720+
var componentName = getComponentNameFromFiber(owner) || "Component";
1721+
1722+
if (!didWarnAboutStringRefs[componentName]) {
1723+
error(
1724+
'Component "%s" contains the string ref "%s". Support for string refs ' +
1725+
"will be removed in a future major release. We recommend using " +
1726+
"useRef() or createRef() instead. " +
1727+
"Learn more about using refs safely here: " +
1728+
"https://react.dev/link/strict-mode-string-ref",
1729+
componentName,
1730+
stringRef
1731+
);
1732+
1733+
didWarnAboutStringRefs[componentName] = true;
1734+
}
1735+
}
1736+
}
1737+
1738+
var inst = owner.stateNode;
1739+
1740+
if (!inst) {
1741+
throw new Error(
1742+
"Missing owner for string ref " +
1743+
stringRef +
1744+
". This error is likely caused by a " +
1745+
"bug in React. Please file an issue."
1746+
);
1747+
}
1748+
1749+
var refs = inst.refs;
1750+
1751+
if (value === null) {
1752+
delete refs[stringRef];
1753+
} else {
1754+
refs[stringRef] = value;
1755+
}
1756+
}
1757+
1758+
function isReactClass(type) {
1759+
return type.prototype && type.prototype.isReactComponent;
1760+
}
1761+
14911762
var jsxDEV = jsxDEV$1;
14921763

14931764
exports.Fragment = REACT_FRAGMENT_TYPE;

0 commit comments

Comments
 (0)