Skip to content

Commit aecaccf

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 commit e3ebcd5.
1 parent 2504236 commit aecaccf

17 files changed

+1563
-1374
lines changed

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react-test-renderer/cjs/ReactTestRenderer-dev.js

Lines changed: 80 additions & 198 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<3151ca79177b1e7b421f14db372bfd97>>
10+
* @generated SignedSource<<98fc293dc2ddd5bd7ffa9ec7eb7ac707>>
1111
*/
1212

1313
"use strict";
@@ -5656,81 +5656,6 @@ if (__DEV__) {
56565656
};
56575657
}
56585658

5659-
/*
5660-
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol
5661-
* and Temporal.* types. See https://github.com/facebook/react/pull/22064.
5662-
*
5663-
* The functions in this module will throw an easier-to-understand,
5664-
* easier-to-debug exception with a clear errors message message explaining the
5665-
* problem. (Instead of a confusing exception thrown inside the implementation
5666-
* of the `value` object).
5667-
*/
5668-
// $FlowFixMe[incompatible-return] only called in DEV, so void return is not possible.
5669-
function typeName(value) {
5670-
{
5671-
// toStringTag is needed for namespaced types like Temporal.Instant
5672-
var hasToStringTag = typeof Symbol === "function" && Symbol.toStringTag;
5673-
var type =
5674-
(hasToStringTag && value[Symbol.toStringTag]) ||
5675-
value.constructor.name ||
5676-
"Object"; // $FlowFixMe[incompatible-return]
5677-
5678-
return type;
5679-
}
5680-
} // $FlowFixMe[incompatible-return] only called in DEV, so void return is not possible.
5681-
5682-
function willCoercionThrow(value) {
5683-
{
5684-
try {
5685-
testStringCoercion(value);
5686-
return false;
5687-
} catch (e) {
5688-
return true;
5689-
}
5690-
}
5691-
}
5692-
5693-
function testStringCoercion(value) {
5694-
// If you ended up here by following an exception call stack, here's what's
5695-
// happened: you supplied an object or symbol value to React (as a prop, key,
5696-
// DOM attribute, CSS property, string ref, etc.) and when React tried to
5697-
// coerce it to a string using `'' + value`, an exception was thrown.
5698-
//
5699-
// The most common types that will cause this exception are `Symbol` instances
5700-
// and Temporal objects like `Temporal.Instant`. But any object that has a
5701-
// `valueOf` or `[Symbol.toPrimitive]` method that throws will also cause this
5702-
// exception. (Library authors do this to prevent users from using built-in
5703-
// numeric operators like `+` or comparison operators like `>=` because custom
5704-
// methods are needed to perform accurate arithmetic or comparison.)
5705-
//
5706-
// To fix the problem, coerce this object or symbol value to a string before
5707-
// passing it to React. The most reliable way is usually `String(value)`.
5708-
//
5709-
// To find which value is throwing, check the browser or debugger console.
5710-
// Before this exception was thrown, there should be `console.error` output
5711-
// that shows the type (Symbol, Temporal.PlainDate, etc.) that caused the
5712-
// problem and how that type was used: key, atrribute, input value prop, etc.
5713-
// In most cases, this console output also shows the component and its
5714-
// ancestor components where the exception happened.
5715-
//
5716-
// eslint-disable-next-line react-internal/safe-string-coercion
5717-
return "" + value;
5718-
}
5719-
function checkPropStringCoercion(value, propName) {
5720-
{
5721-
if (willCoercionThrow(value)) {
5722-
error(
5723-
"The provided `%s` prop is an unsupported type %s." +
5724-
" This value must be coerced to a string before using it here.",
5725-
propName,
5726-
typeName(value)
5727-
);
5728-
5729-
return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
5730-
}
5731-
}
5732-
}
5733-
57345659
var ReactCurrentActQueue$3 = ReactSharedInternals.ReactCurrentActQueue;
57355660

57365661
function getThenablesFromState(state) {
@@ -6010,7 +5935,6 @@ if (__DEV__) {
60105935

60115936
var didWarnAboutMaps;
60125937
var didWarnAboutGenerators;
6013-
var didWarnAboutStringRefs;
60145938
var ownerHasKeyUseWarning;
60155939
var ownerHasFunctionTypeWarning;
60165940
var ownerHasSymbolTypeWarning;
@@ -6020,7 +5944,6 @@ if (__DEV__) {
60205944
{
60215945
didWarnAboutMaps = false;
60225946
didWarnAboutGenerators = false;
6023-
didWarnAboutStringRefs = {};
60245947
/**
60255948
* Warn if there's no key explicitly set on dynamic arrays of children or
60265949
* object keys are not valid. This allows us to keep track of children between
@@ -6065,10 +5988,6 @@ if (__DEV__) {
60655988
};
60665989
}
60675990

6068-
function isReactClass(type) {
6069-
return type.prototype && type.prototype.isReactComponent;
6070-
}
6071-
60725991
function unwrapThenable(thenable) {
60735992
var index = thenableIndexCounter$1;
60745993
thenableIndexCounter$1 += 1;
@@ -6080,128 +5999,16 @@ if (__DEV__) {
60805999
return trackUsedThenable(thenableState$1, thenable, index);
60816000
}
60826001

6083-
function convertStringRefToCallbackRef(
6084-
returnFiber,
6085-
current,
6086-
element,
6087-
mixedRef
6088-
) {
6089-
{
6090-
checkPropStringCoercion(mixedRef, "ref");
6091-
}
6092-
6093-
var stringRef = "" + mixedRef;
6094-
var owner = element._owner;
6095-
6096-
if (!owner) {
6097-
throw new Error(
6098-
"Element ref was specified as a string (" +
6099-
stringRef +
6100-
") but no owner was set. This could happen for one of" +
6101-
" the following reasons:\n" +
6102-
"1. You may be adding a ref to a function component\n" +
6103-
"2. You may be adding a ref to a component that was not created inside a component's render method\n" +
6104-
"3. You have multiple copies of React loaded\n" +
6105-
"See https://react.dev/link/refs-must-have-owner for more information."
6106-
);
6107-
}
6108-
6109-
if (owner.tag !== ClassComponent) {
6110-
throw new Error(
6111-
"Function components cannot have string refs. " +
6112-
"We recommend using useRef() instead. " +
6113-
"Learn more about using refs safely here: " +
6114-
"https://react.dev/link/strict-mode-string-ref"
6115-
);
6116-
}
6117-
6118-
{
6119-
if (
6120-
// Will already warn with "Function components cannot be given refs"
6121-
!(typeof element.type === "function" && !isReactClass(element.type))
6122-
) {
6123-
var componentName =
6124-
getComponentNameFromFiber(returnFiber) || "Component";
6125-
6126-
if (!didWarnAboutStringRefs[componentName]) {
6127-
error(
6128-
'Component "%s" contains the string ref "%s". Support for string refs ' +
6129-
"will be removed in a future major release. We recommend using " +
6130-
"useRef() or createRef() instead. " +
6131-
"Learn more about using refs safely here: " +
6132-
"https://react.dev/link/strict-mode-string-ref",
6133-
componentName,
6134-
stringRef
6135-
);
6136-
6137-
didWarnAboutStringRefs[componentName] = true;
6138-
}
6139-
}
6140-
}
6141-
6142-
var inst = owner.stateNode;
6143-
6144-
if (!inst) {
6145-
throw new Error(
6146-
"Missing owner for string ref " +
6147-
stringRef +
6148-
". This error is likely caused by a " +
6149-
"bug in React. Please file an issue."
6150-
);
6151-
} // Check if previous string ref matches new string ref
6152-
6153-
if (
6154-
current !== null &&
6155-
current.ref !== null &&
6156-
typeof current.ref === "function" &&
6157-
current.ref._stringRef === stringRef
6158-
) {
6159-
// Reuse the existing string ref
6160-
var currentRef = current.ref;
6161-
return currentRef;
6162-
} // Create a new string ref
6163-
6164-
var ref = function (value) {
6165-
var refs = inst.refs;
6166-
6167-
if (value === null) {
6168-
delete refs[stringRef];
6169-
} else {
6170-
refs[stringRef] = value;
6171-
}
6172-
};
6173-
6174-
ref._stringRef = stringRef;
6175-
return ref;
6176-
}
6177-
61786002
function coerceRef(returnFiber, current, workInProgress, element) {
6179-
var mixedRef;
6003+
var ref;
61806004

61816005
{
61826006
// Old behavior.
6183-
mixedRef = element.ref;
6184-
}
6185-
6186-
var coercedRef;
6187-
6188-
if (
6189-
typeof mixedRef === "string" ||
6190-
typeof mixedRef === "number" ||
6191-
typeof mixedRef === "boolean"
6192-
) {
6193-
coercedRef = convertStringRefToCallbackRef(
6194-
returnFiber,
6195-
current,
6196-
element,
6197-
mixedRef
6198-
);
6199-
} else {
6200-
coercedRef = mixedRef;
6007+
ref = element.ref;
62016008
} // TODO: If enableRefAsProp is on, we shouldn't use the `ref` field. We
62026009
// should always read the ref from the prop.
62036010

6204-
workInProgress.ref = coercedRef;
6011+
workInProgress.ref = ref;
62056012
}
62066013

62076014
function throwOnInvalidObjectType(returnFiber, newChild) {
@@ -26823,7 +26630,82 @@ if (__DEV__) {
2682326630
return root;
2682426631
}
2682526632

26826-
var ReactVersion = "19.0.0-canary-29bd6113";
26633+
var ReactVersion = "19.0.0-canary-fbd6543d";
26634+
26635+
/*
26636+
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol
26637+
* and Temporal.* types. See https://github.com/facebook/react/pull/22064.
26638+
*
26639+
* The functions in this module will throw an easier-to-understand,
26640+
* easier-to-debug exception with a clear errors message message explaining the
26641+
* problem. (Instead of a confusing exception thrown inside the implementation
26642+
* of the `value` object).
26643+
*/
26644+
// $FlowFixMe[incompatible-return] only called in DEV, so void return is not possible.
26645+
function typeName(value) {
26646+
{
26647+
// toStringTag is needed for namespaced types like Temporal.Instant
26648+
var hasToStringTag = typeof Symbol === "function" && Symbol.toStringTag;
26649+
var type =
26650+
(hasToStringTag && value[Symbol.toStringTag]) ||
26651+
value.constructor.name ||
26652+
"Object"; // $FlowFixMe[incompatible-return]
26653+
26654+
return type;
26655+
}
26656+
} // $FlowFixMe[incompatible-return] only called in DEV, so void return is not possible.
26657+
26658+
function willCoercionThrow(value) {
26659+
{
26660+
try {
26661+
testStringCoercion(value);
26662+
return false;
26663+
} catch (e) {
26664+
return true;
26665+
}
26666+
}
26667+
}
26668+
26669+
function testStringCoercion(value) {
26670+
// If you ended up here by following an exception call stack, here's what's
26671+
// happened: you supplied an object or symbol value to React (as a prop, key,
26672+
// DOM attribute, CSS property, string ref, etc.) and when React tried to
26673+
// coerce it to a string using `'' + value`, an exception was thrown.
26674+
//
26675+
// The most common types that will cause this exception are `Symbol` instances
26676+
// and Temporal objects like `Temporal.Instant`. But any object that has a
26677+
// `valueOf` or `[Symbol.toPrimitive]` method that throws will also cause this
26678+
// exception. (Library authors do this to prevent users from using built-in
26679+
// numeric operators like `+` or comparison operators like `>=` because custom
26680+
// methods are needed to perform accurate arithmetic or comparison.)
26681+
//
26682+
// To fix the problem, coerce this object or symbol value to a string before
26683+
// passing it to React. The most reliable way is usually `String(value)`.
26684+
//
26685+
// To find which value is throwing, check the browser or debugger console.
26686+
// Before this exception was thrown, there should be `console.error` output
26687+
// that shows the type (Symbol, Temporal.PlainDate, etc.) that caused the
26688+
// problem and how that type was used: key, atrribute, input value prop, etc.
26689+
// In most cases, this console output also shows the component and its
26690+
// ancestor components where the exception happened.
26691+
//
26692+
// eslint-disable-next-line react-internal/safe-string-coercion
26693+
return "" + value;
26694+
}
26695+
function checkPropStringCoercion(value, propName) {
26696+
{
26697+
if (willCoercionThrow(value)) {
26698+
error(
26699+
"The provided `%s` prop is an unsupported type %s." +
26700+
" This value must be coerced to a string before using it here.",
26701+
propName,
26702+
typeName(value)
26703+
);
26704+
26705+
return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
26706+
}
26707+
}
26708+
}
2682726709

2682826710
// Might add PROFILE later.
2682926711

0 commit comments

Comments
 (0)