Skip to content

Commit 90e6a90

Browse files
committed
Extract string ref logic to separate function
Preparing to put this behind a flag. Should not affect any behavior on its own.
1 parent cefc1c6 commit 90e6a90

File tree

1 file changed

+117
-105
lines changed

1 file changed

+117
-105
lines changed

packages/react-reconciler/src/ReactChildFiber.js

Lines changed: 117 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -149,128 +149,140 @@ function unwrapThenable<T>(thenable: Thenable<T>): T {
149149
return trackUsedThenable(thenableState, thenable, index);
150150
}
151151

152+
type CoercedStringRef = ((handle: mixed) => void) & {_stringRef: ?string, ...};
153+
154+
function convertStringRefToCallbackRef(
155+
returnFiber: Fiber,
156+
current: Fiber | null,
157+
element: ReactElement,
158+
mixedRef: any,
159+
): CoercedStringRef {
160+
const owner: ?Fiber = (element._owner: any);
161+
if (!owner) {
162+
if (typeof mixedRef !== 'string') {
163+
throw new Error(
164+
'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
165+
);
166+
}
167+
throw new Error(
168+
`Element ref was specified as a string (${mixedRef}) but no owner was set. This could happen for one of` +
169+
' the following reasons:\n' +
170+
'1. You may be adding a ref to a function component\n' +
171+
"2. You may be adding a ref to a component that was not created inside a component's render method\n" +
172+
'3. You have multiple copies of React loaded\n' +
173+
'See https://reactjs.org/link/refs-must-have-owner for more information.',
174+
);
175+
}
176+
if (owner.tag !== ClassComponent) {
177+
throw new Error(
178+
'Function components cannot have string refs. ' +
179+
'We recommend using useRef() instead. ' +
180+
'Learn more about using refs safely here: ' +
181+
'https://reactjs.org/link/strict-mode-string-ref',
182+
);
183+
}
184+
185+
// At this point, we know the ref isn't an object or function but it could
186+
// be a number. Coerce it to a string.
187+
if (__DEV__) {
188+
checkPropStringCoercion(mixedRef, 'ref');
189+
}
190+
const stringRef = '' + mixedRef;
191+
192+
if (__DEV__) {
193+
if (
194+
// Will already warn with "Function components cannot be given refs"
195+
!(typeof element.type === 'function' && !isReactClass(element.type))
196+
) {
197+
const componentName =
198+
getComponentNameFromFiber(returnFiber) || 'Component';
199+
if (!didWarnAboutStringRefs[componentName]) {
200+
console.error(
201+
'Component "%s" contains the string ref "%s". Support for string refs ' +
202+
'will be removed in a future major release. We recommend using ' +
203+
'useRef() or createRef() instead. ' +
204+
'Learn more about using refs safely here: ' +
205+
'https://reactjs.org/link/strict-mode-string-ref',
206+
componentName,
207+
stringRef,
208+
);
209+
didWarnAboutStringRefs[componentName] = true;
210+
}
211+
}
212+
}
213+
214+
const inst = owner.stateNode;
215+
if (!inst) {
216+
throw new Error(
217+
`Missing owner for string ref ${stringRef}. This error is likely caused by a ` +
218+
'bug in React. Please file an issue.',
219+
);
220+
}
221+
222+
// Check if previous string ref matches new string ref
223+
if (
224+
current !== null &&
225+
current.ref !== null &&
226+
typeof current.ref === 'function' &&
227+
current.ref._stringRef === stringRef
228+
) {
229+
// Reuse the existing string ref
230+
const currentRef: CoercedStringRef = ((current.ref: any): CoercedStringRef);
231+
return currentRef;
232+
}
233+
234+
// Create a new string ref
235+
const ref = function (value: mixed) {
236+
const refs = inst.refs;
237+
if (value === null) {
238+
delete refs[stringRef];
239+
} else {
240+
refs[stringRef] = value;
241+
}
242+
};
243+
ref._stringRef = stringRef;
244+
return ref;
245+
}
246+
152247
function coerceRef(
153248
returnFiber: Fiber,
154249
current: Fiber | null,
250+
workInProgress: Fiber,
155251
element: ReactElement,
156-
) {
252+
): void {
157253
let mixedRef;
158254
if (enableRefAsProp) {
159255
// TODO: This is a temporary, intermediate step. When enableRefAsProp is on,
160256
// we should resolve the `ref` prop during the begin phase of the component
161257
// it's attached to (HostComponent, ClassComponent, etc).
162-
163258
const refProp = element.props.ref;
164259
mixedRef = refProp !== undefined ? refProp : null;
165260
} else {
166261
// Old behavior.
167262
mixedRef = element.ref;
168263
}
169264

265+
let coercedRef;
170266
if (
171267
mixedRef !== null &&
172268
typeof mixedRef !== 'function' &&
173269
typeof mixedRef !== 'object'
174270
) {
175-
if (__DEV__) {
176-
if (
177-
// Will already throw with "Function components cannot have string refs"
178-
!(
179-
element._owner &&
180-
((element._owner: any): Fiber).tag !== ClassComponent
181-
) &&
182-
// Will already warn with "Function components cannot be given refs"
183-
!(typeof element.type === 'function' && !isReactClass(element.type)) &&
184-
// Will already throw with "Element ref was specified as a string (someStringRef) but no owner was set"
185-
element._owner
186-
) {
187-
const componentName =
188-
getComponentNameFromFiber(returnFiber) || 'Component';
189-
if (!didWarnAboutStringRefs[componentName]) {
190-
console.error(
191-
'Component "%s" contains the string ref "%s". Support for string refs ' +
192-
'will be removed in a future major release. We recommend using ' +
193-
'useRef() or createRef() instead. ' +
194-
'Learn more about using refs safely here: ' +
195-
'https://reactjs.org/link/strict-mode-string-ref',
196-
componentName,
197-
mixedRef,
198-
);
199-
didWarnAboutStringRefs[componentName] = true;
200-
}
201-
}
202-
}
203-
204-
if (element._owner) {
205-
const owner: ?Fiber = (element._owner: any);
206-
let inst;
207-
if (owner) {
208-
const ownerFiber = ((owner: any): Fiber);
209-
210-
if (ownerFiber.tag !== ClassComponent) {
211-
throw new Error(
212-
'Function components cannot have string refs. ' +
213-
'We recommend using useRef() instead. ' +
214-
'Learn more about using refs safely here: ' +
215-
'https://reactjs.org/link/strict-mode-string-ref',
216-
);
217-
}
218-
219-
inst = ownerFiber.stateNode;
220-
}
221-
222-
if (!inst) {
223-
throw new Error(
224-
`Missing owner for string ref ${mixedRef}. This error is likely caused by a ` +
225-
'bug in React. Please file an issue.',
226-
);
227-
}
228-
// Assigning this to a const so Flow knows it won't change in the closure
229-
const resolvedInst = inst;
230-
231-
if (__DEV__) {
232-
checkPropStringCoercion(mixedRef, 'ref');
233-
}
234-
const stringRef = '' + mixedRef;
235-
// Check if previous string ref matches new string ref
236-
if (
237-
current !== null &&
238-
current.ref !== null &&
239-
typeof current.ref === 'function' &&
240-
current.ref._stringRef === stringRef
241-
) {
242-
return current.ref;
243-
}
244-
const ref = function (value: mixed) {
245-
const refs = resolvedInst.refs;
246-
if (value === null) {
247-
delete refs[stringRef];
248-
} else {
249-
refs[stringRef] = value;
250-
}
251-
};
252-
ref._stringRef = stringRef;
253-
return ref;
254-
} else {
255-
if (typeof mixedRef !== 'string') {
256-
throw new Error(
257-
'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
258-
);
259-
}
260-
261-
if (!element._owner) {
262-
throw new Error(
263-
`Element ref was specified as a string (${mixedRef}) but no owner was set. This could happen for one of` +
264-
' the following reasons:\n' +
265-
'1. You may be adding a ref to a function component\n' +
266-
"2. You may be adding a ref to a component that was not created inside a component's render method\n" +
267-
'3. You have multiple copies of React loaded\n' +
268-
'See https://reactjs.org/link/refs-must-have-owner for more information.',
269-
);
270-
}
271-
}
271+
// Assume this is a string ref. If it's not, then this will throw an error
272+
// to the user.
273+
coercedRef = convertStringRefToCallbackRef(
274+
returnFiber,
275+
current,
276+
element,
277+
mixedRef,
278+
);
279+
} else {
280+
coercedRef = mixedRef;
272281
}
273-
return mixedRef;
282+
283+
// TODO: If enableRefAsProp is on, we shouldn't use the `ref` field. We
284+
// should always read the ref from the prop.
285+
workInProgress.ref = coercedRef;
274286
}
275287

276288
function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
@@ -537,7 +549,7 @@ function createChildReconciler(
537549
) {
538550
// Move based on index
539551
const existing = useFiber(current, element.props);
540-
existing.ref = coerceRef(returnFiber, current, element);
552+
coerceRef(returnFiber, current, existing, element);
541553
existing.return = returnFiber;
542554
if (__DEV__) {
543555
existing._debugOwner = element._owner;
@@ -548,7 +560,7 @@ function createChildReconciler(
548560
}
549561
// Insert
550562
const created = createFiberFromElement(element, returnFiber.mode, lanes);
551-
created.ref = coerceRef(returnFiber, current, element);
563+
coerceRef(returnFiber, current, created, element);
552564
created.return = returnFiber;
553565
if (__DEV__) {
554566
created._debugInfo = debugInfo;
@@ -652,7 +664,7 @@ function createChildReconciler(
652664
returnFiber.mode,
653665
lanes,
654666
);
655-
created.ref = coerceRef(returnFiber, null, newChild);
667+
coerceRef(returnFiber, null, created, newChild);
656668
created.return = returnFiber;
657669
if (__DEV__) {
658670
created._debugInfo = mergeDebugInfo(debugInfo, newChild._debugInfo);
@@ -1481,7 +1493,7 @@ function createChildReconciler(
14811493
) {
14821494
deleteRemainingChildren(returnFiber, child.sibling);
14831495
const existing = useFiber(child, element.props);
1484-
existing.ref = coerceRef(returnFiber, child, element);
1496+
coerceRef(returnFiber, child, existing, element);
14851497
existing.return = returnFiber;
14861498
if (__DEV__) {
14871499
existing._debugOwner = element._owner;
@@ -1513,7 +1525,7 @@ function createChildReconciler(
15131525
return created;
15141526
} else {
15151527
const created = createFiberFromElement(element, returnFiber.mode, lanes);
1516-
created.ref = coerceRef(returnFiber, currentFirstChild, element);
1528+
coerceRef(returnFiber, currentFirstChild, created, element);
15171529
created.return = returnFiber;
15181530
if (__DEV__) {
15191531
created._debugInfo = debugInfo;

0 commit comments

Comments
 (0)