41
Fragment,
42
} from './ReactWorkTags';
43
import isArray from 'shared/isArray';
44
+import assign from 'shared/assign';
45
import {checkPropStringCoercion} from 'shared/CheckStringCoercion';
46
import {enableRefAsProp} from 'shared/ReactFeatureFlags';
47
150
return trackUsedThenable(thenableState, thenable, index);
151
}
152
153
+type CoercedStringRef = ((handle: mixed) => void) & {_stringRef: ?string, ...};
154
+
155
+function convertStringRefToCallbackRef(
156
+ returnFiber: Fiber,
157
+ current: Fiber | null,
158
+ element: ReactElement,
159
+ mixedRef: any,
160
+): CoercedStringRef {
161
+ const owner: ?Fiber = (element._owner: any);
162
+ if (!owner) {
163
+ if (typeof mixedRef !== 'string') {
164
+ throw new Error(
165
+ 'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
166
+ );
167
+ }
168
+ throw new Error(
169
+ `Element ref was specified as a string (${mixedRef}) but no owner was set. This could happen for one of` +
170
+ ' the following reasons:\n' +
171
+ '1. You may be adding a ref to a function component\n' +
172
+ "2. You may be adding a ref to a component that was not created inside a component's render method\n" +
173
+ '3. You have multiple copies of React loaded\n' +
174
+ 'See https://reactjs.org/link/refs-must-have-owner for more information.',
175
+ );
176
+ }
177
+ if (owner.tag !== ClassComponent) {
178
+ throw new Error(
179
+ 'Function components cannot have string refs. ' +
180
+ 'We recommend using useRef() instead. ' +
181
+ 'Learn more about using refs safely here: ' +
182
+ 'https://reactjs.org/link/strict-mode-string-ref',
183
+ );
184
+ }
185
+
186
+ // At this point, we know the ref isn't an object or function but it could
187
+ // be a number. Coerce it to a string.
188
+ if (__DEV__) {
189
+ checkPropStringCoercion(mixedRef, 'ref');
190
+ }
191
+ const stringRef = '' + mixedRef;
192
+
193
+ if (__DEV__) {
194
+ if (
195
+ // Will already warn with "Function components cannot be given refs"
196
+ !(typeof element.type === 'function' && !isReactClass(element.type))
197
+ ) {
198
+ const componentName =
199
+ getComponentNameFromFiber(returnFiber) || 'Component';
200
+ if (!didWarnAboutStringRefs[componentName]) {
201
+ console.error(
202
+ 'Component "%s" contains the string ref "%s". Support for string refs ' +
203
+ 'will be removed in a future major release. We recommend using ' +
204
+ 'useRef() or createRef() instead. ' +
205
+ 'Learn more about using refs safely here: ' +
206
+ 'https://reactjs.org/link/strict-mode-string-ref',
207
+ componentName,
208
+ stringRef,
209
+ );
210
+ didWarnAboutStringRefs[componentName] = true;
211
+ }
212
+ }
213
+ }
214
+
215
+ const inst = owner.stateNode;
216
+ if (!inst) {
217
+ throw new Error(
218
+ `Missing owner for string ref ${stringRef}. This error is likely caused by a ` +
219
+ 'bug in React. Please file an issue.',
220
+ );
221
+ }
222
+
223
+ // Check if previous string ref matches new string ref
224
+ if (
225
+ current !== null &&
226
+ current.ref !== null &&
227
+ typeof current.ref === 'function' &&
228
+ current.ref._stringRef === stringRef
229
+ ) {
230
+ // Reuse the existing string ref
231
+ const currentRef: CoercedStringRef = ((current.ref: any): CoercedStringRef);
232
+ return currentRef;
233
+ }
234
+
235
+ // Create a new string ref
236
+ const ref = function (value: mixed) {
237
+ const refs = inst.refs;
238
+ if (value === null) {
239
+ delete refs[stringRef];
240
+ } else {
241
+ refs[stringRef] = value;
242
+ }
243
+ };
244
+ ref._stringRef = stringRef;
245
+ return ref;
246
+}
247
+
248
function coerceRef(
249
returnFiber: Fiber,
250
current: Fiber | null,
251
+ workInProgress: Fiber,
252
element: ReactElement,
156
-) {
253
+): void {
254
let mixedRef;
255
if (enableRefAsProp) {
256
// TODO: This is a temporary, intermediate step. When enableRefAsProp is on,
257
// we should resolve the `ref` prop during the begin phase of the component
258
// it's attached to (HostComponent, ClassComponent, etc).
162
-
259
const refProp = element.props.ref;
260
mixedRef = refProp !== undefined ? refProp : null;
261
} else {
263
mixedRef = element.ref;
264
}
265
266
+ let coercedRef;
267
if (
268
mixedRef !== null &&
269
typeof mixedRef !== 'function' &&
270
typeof mixedRef !== 'object'
271
) {
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
- }
272
+ // Assume this is a string ref. If it's not, then this will throw an error
273
+ // to the user.
274
+ coercedRef = convertStringRefToCallbackRef(
275
+ returnFiber,
276
+ current,
277
+ element,
278
+ mixedRef,
279
+ );
280
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
- }
281
+ if (enableRefAsProp) {
282
+ // When enableRefAsProp is on, we should always use the props as the
283
+ // source of truth for refs. Not a field on the fiber.
284
+ //
285
+ // In the case of string refs, this presents a problem, because string
286
+ // refs are not passed around internally as strings; they are converted to
287
+ // callback refs. The ref used by the reconciler is not the same as the
288
+ // one the user provided.
289
+ //
290
+ // But since this is a deprecated feature anyway, what we can do is clone
291
+ // the props object and replace it with the internal callback ref. Then we
292
+ // can continue to use the props object as the source of truth.
293
+ //
294
+ // This means the internal callback ref will leak into userspace. The
295
+ // receiving component will receive a callback ref even though the parent
296
+ // passed a string. Which is weird, but again, this is a deprecated
297
+ // feature, and we're only leaving it around behind a flag so that Meta
298
+ // can keep using string refs temporarily while they finish migrating
299
+ // their codebase.
300
+ const userProvidedProps = workInProgress.pendingProps;
301
+ const propsWithInternalCallbackRef = assign({}, userProvidedProps);
302
+ propsWithInternalCallbackRef.ref = coercedRef;
303
+ workInProgress.pendingProps = propsWithInternalCallbackRef;
304
}
305
+ } else {
306
+ coercedRef = mixedRef;
307
}
273
- return mixedRef;
308
+
309
+ // TODO: If enableRefAsProp is on, we shouldn't use the `ref` field. We
310
+ // should always read the ref from the prop.
311
+ workInProgress.ref = coercedRef;
312
}
313
314
function throwOnInvalidObjectType(returnFiber: Fiber, newChild: Object) {
575
) {
576
// Move based on index
577
const existing = useFiber(current, element.props);
540
- existing.ref = coerceRef(returnFiber, current, element);
578
+ coerceRef(returnFiber, current, existing, element);
579
existing.return = returnFiber;
580
if (__DEV__) {
581
existing._debugOwner = element._owner;
586
}
587
// Insert
588
const created = createFiberFromElement(element, returnFiber.mode, lanes);
551
- created.ref = coerceRef(returnFiber, current, element);
589
+ coerceRef(returnFiber, current, created, element);
590
created.return = returnFiber;
591
if (__DEV__) {
592
created._debugInfo = debugInfo;
690
returnFiber.mode,
691
lanes,
692
);
655
- created.ref = coerceRef(returnFiber, null, newChild);
693
+ coerceRef(returnFiber, null, created, newChild);
694
created.return = returnFiber;
695
if (__DEV__) {
696
created._debugInfo = mergeDebugInfo(debugInfo, newChild._debugInfo);
1519
) {
1520
deleteRemainingChildren(returnFiber, child.sibling);
1521
const existing = useFiber(child, element.props);
1484
- existing.ref = coerceRef(returnFiber, child, element);
1522
+ coerceRef(returnFiber, child, existing, element);
1523
existing.return = returnFiber;
1524
if (__DEV__) {
1525
existing._debugOwner = element._owner;
1551
return created;
1552
} else {
1553
const created = createFiberFromElement(element, returnFiber.mode, lanes);
1516
- created.ref = coerceRef(returnFiber, currentFirstChild, element);
1554
+ coerceRef(returnFiber, currentFirstChild, created, element);
1555
created.return = returnFiber;
1556
if (__DEV__) {
1557
created._debugInfo = debugInfo;