Skip to content

Commit

Permalink
Extract string ref logic to separate function
Browse files Browse the repository at this point in the history
Preparing to put this behind a flag. Should not affect any behavior on
its own.
  • Loading branch information
acdlite committed Feb 20, 2024
1 parent cefc1c6 commit 90e6a90
Showing 1 changed file with 117 additions and 105 deletions.
222 changes: 117 additions & 105 deletions packages/react-reconciler/src/ReactChildFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,128 +149,140 @@ function unwrapThenable<T>(thenable: Thenable<T>): T {
return trackUsedThenable(thenableState, thenable, index);
}

type CoercedStringRef = ((handle: mixed) => void) & {_stringRef: ?string, ...};

function convertStringRefToCallbackRef(
returnFiber: Fiber,
current: Fiber | null,
element: ReactElement,
mixedRef: any,
): CoercedStringRef {
const owner: ?Fiber = (element._owner: any);
if (!owner) {
if (typeof mixedRef !== 'string') {
throw new Error(
'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
);
}
throw new Error(
`Element ref was specified as a string (${mixedRef}) but no owner was set. This could happen for one of` +
' the following reasons:\n' +
'1. You may be adding a ref to a function component\n' +
"2. You may be adding a ref to a component that was not created inside a component's render method\n" +
'3. You have multiple copies of React loaded\n' +
'See https://reactjs.org/link/refs-must-have-owner for more information.',
);
}
if (owner.tag !== ClassComponent) {
throw new Error(
'Function components cannot have string refs. ' +
'We recommend using useRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://reactjs.org/link/strict-mode-string-ref',
);
}

// At this point, we know the ref isn't an object or function but it could
// be a number. Coerce it to a string.
if (__DEV__) {
checkPropStringCoercion(mixedRef, 'ref');
}
const stringRef = '' + mixedRef;

if (__DEV__) {
if (
// Will already warn with "Function components cannot be given refs"
!(typeof element.type === 'function' && !isReactClass(element.type))
) {
const componentName =
getComponentNameFromFiber(returnFiber) || 'Component';
if (!didWarnAboutStringRefs[componentName]) {
console.error(
'Component "%s" contains the string ref "%s". Support for string refs ' +
'will be removed in a future major release. We recommend using ' +
'useRef() or createRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://reactjs.org/link/strict-mode-string-ref',
componentName,
stringRef,
);
didWarnAboutStringRefs[componentName] = true;
}
}
}

const inst = owner.stateNode;
if (!inst) {
throw new Error(
`Missing owner for string ref ${stringRef}. This error is likely caused by a ` +
'bug in React. Please file an issue.',
);
}

// Check if previous string ref matches new string ref
if (
current !== null &&
current.ref !== null &&
typeof current.ref === 'function' &&
current.ref._stringRef === stringRef
) {
// Reuse the existing string ref
const currentRef: CoercedStringRef = ((current.ref: any): CoercedStringRef);
return currentRef;
}

// Create a new string ref
const ref = function (value: mixed) {
const refs = inst.refs;
if (value === null) {
delete refs[stringRef];
} else {
refs[stringRef] = value;
}
};
ref._stringRef = stringRef;
return ref;
}

function coerceRef(
returnFiber: Fiber,
current: Fiber | null,
workInProgress: Fiber,
element: ReactElement,
) {
): void {
let mixedRef;
if (enableRefAsProp) {
// TODO: This is a temporary, intermediate step. When enableRefAsProp is on,
// we should resolve the `ref` prop during the begin phase of the component
// it's attached to (HostComponent, ClassComponent, etc).

const refProp = element.props.ref;
mixedRef = refProp !== undefined ? refProp : null;
} else {
// Old behavior.
mixedRef = element.ref;
}

let coercedRef;
if (
mixedRef !== null &&
typeof mixedRef !== 'function' &&
typeof mixedRef !== 'object'
) {
if (__DEV__) {
if (
// Will already throw with "Function components cannot have string refs"
!(
element._owner &&
((element._owner: any): Fiber).tag !== ClassComponent
) &&
// Will already warn with "Function components cannot be given refs"
!(typeof element.type === 'function' && !isReactClass(element.type)) &&
// Will already throw with "Element ref was specified as a string (someStringRef) but no owner was set"
element._owner
) {
const componentName =
getComponentNameFromFiber(returnFiber) || 'Component';
if (!didWarnAboutStringRefs[componentName]) {
console.error(
'Component "%s" contains the string ref "%s". Support for string refs ' +
'will be removed in a future major release. We recommend using ' +
'useRef() or createRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://reactjs.org/link/strict-mode-string-ref',
componentName,
mixedRef,
);
didWarnAboutStringRefs[componentName] = true;
}
}
}

if (element._owner) {
const owner: ?Fiber = (element._owner: any);
let inst;
if (owner) {
const ownerFiber = ((owner: any): Fiber);

if (ownerFiber.tag !== ClassComponent) {
throw new Error(
'Function components cannot have string refs. ' +
'We recommend using useRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://reactjs.org/link/strict-mode-string-ref',
);
}

inst = ownerFiber.stateNode;
}

if (!inst) {
throw new Error(
`Missing owner for string ref ${mixedRef}. This error is likely caused by a ` +
'bug in React. Please file an issue.',
);
}
// Assigning this to a const so Flow knows it won't change in the closure
const resolvedInst = inst;

if (__DEV__) {
checkPropStringCoercion(mixedRef, 'ref');
}
const stringRef = '' + mixedRef;
// Check if previous string ref matches new string ref
if (
current !== null &&
current.ref !== null &&
typeof current.ref === 'function' &&
current.ref._stringRef === stringRef
) {
return current.ref;
}
const ref = function (value: mixed) {
const refs = resolvedInst.refs;
if (value === null) {
delete refs[stringRef];
} else {
refs[stringRef] = value;
}
};
ref._stringRef = stringRef;
return ref;
} else {
if (typeof mixedRef !== 'string') {
throw new Error(
'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
);
}

if (!element._owner) {
throw new Error(
`Element ref was specified as a string (${mixedRef}) but no owner was set. This could happen for one of` +
' the following reasons:\n' +
'1. You may be adding a ref to a function component\n' +
"2. You may be adding a ref to a component that was not created inside a component's render method\n" +
'3. You have multiple copies of React loaded\n' +
'See https://reactjs.org/link/refs-must-have-owner for more information.',
);
}
}
// Assume this is a string ref. If it's not, then this will throw an error
// to the user.
coercedRef = convertStringRefToCallbackRef(
returnFiber,
current,
element,
mixedRef,
);
} else {
coercedRef = mixedRef;
}
return mixedRef;

// TODO: If enableRefAsProp is on, we shouldn't use the `ref` field. We
// should always read the ref from the prop.
workInProgress.ref = coercedRef;
}

function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
Expand Down Expand Up @@ -537,7 +549,7 @@ function createChildReconciler(
) {
// Move based on index
const existing = useFiber(current, element.props);
existing.ref = coerceRef(returnFiber, current, element);
coerceRef(returnFiber, current, existing, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugOwner = element._owner;
Expand All @@ -548,7 +560,7 @@ function createChildReconciler(
}
// Insert
const created = createFiberFromElement(element, returnFiber.mode, lanes);
created.ref = coerceRef(returnFiber, current, element);
coerceRef(returnFiber, current, created, element);
created.return = returnFiber;
if (__DEV__) {
created._debugInfo = debugInfo;
Expand Down Expand Up @@ -652,7 +664,7 @@ function createChildReconciler(
returnFiber.mode,
lanes,
);
created.ref = coerceRef(returnFiber, null, newChild);
coerceRef(returnFiber, null, created, newChild);
created.return = returnFiber;
if (__DEV__) {
created._debugInfo = mergeDebugInfo(debugInfo, newChild._debugInfo);
Expand Down Expand Up @@ -1481,7 +1493,7 @@ function createChildReconciler(
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.ref = coerceRef(returnFiber, child, element);
coerceRef(returnFiber, child, existing, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugOwner = element._owner;
Expand Down Expand Up @@ -1513,7 +1525,7 @@ function createChildReconciler(
return created;
} else {
const created = createFiberFromElement(element, returnFiber.mode, lanes);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
coerceRef(returnFiber, currentFirstChild, created, element);
created.return = returnFiber;
if (__DEV__) {
created._debugInfo = debugInfo;
Expand Down

0 comments on commit 90e6a90

Please sign in to comment.