@@ -41,6 +41,7 @@ import {
41
41
Fragment ,
42
42
} from './ReactWorkTags' ;
43
43
import isArray from 'shared/isArray' ;
44
+ import assign from 'shared/assign' ;
44
45
import { checkPropStringCoercion } from 'shared/CheckStringCoercion' ;
45
46
import { enableRefAsProp } from 'shared/ReactFeatureFlags' ;
46
47
@@ -149,128 +150,165 @@ function unwrapThenable<T>(thenable: Thenable<T>): T {
149
150
return trackUsedThenable ( thenableState , thenable , index ) ;
150
151
}
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
+
152
248
function coerceRef (
153
249
returnFiber : Fiber ,
154
250
current : Fiber | null ,
251
+ workInProgress : Fiber ,
155
252
element : ReactElement ,
156
- ) {
253
+ ) : void {
157
254
let mixedRef ;
158
255
if ( enableRefAsProp ) {
159
256
// TODO: This is a temporary, intermediate step. When enableRefAsProp is on,
160
257
// we should resolve the `ref` prop during the begin phase of the component
161
258
// it's attached to (HostComponent, ClassComponent, etc).
162
-
163
259
const refProp = element . props . ref ;
164
260
mixedRef = refProp !== undefined ? refProp : null ;
165
261
} else {
166
262
// Old behavior.
167
263
mixedRef = element . ref ;
168
264
}
169
265
266
+ let coercedRef ;
170
267
if (
171
268
mixedRef !== null &&
172
269
typeof mixedRef !== 'function' &&
173
270
typeof mixedRef !== 'object'
174
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
+ ) ;
260
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 ;
271
304
}
305
+ } else {
306
+ coercedRef = mixedRef ;
272
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 ;
274
312
}
275
313
276
314
function throwOnInvalidObjectType ( returnFiber : Fiber , newChild : Object ) {
@@ -537,7 +575,7 @@ function createChildReconciler(
537
575
) {
538
576
// Move based on index
539
577
const existing = useFiber ( current , element . props ) ;
540
- existing . ref = coerceRef ( returnFiber , current , element ) ;
578
+ coerceRef ( returnFiber , current , existing , element ) ;
541
579
existing . return = returnFiber ;
542
580
if ( __DEV__ ) {
543
581
existing . _debugOwner = element . _owner ;
@@ -548,7 +586,7 @@ function createChildReconciler(
548
586
}
549
587
// Insert
550
588
const created = createFiberFromElement ( element , returnFiber . mode , lanes ) ;
551
- created . ref = coerceRef ( returnFiber , current , element ) ;
589
+ coerceRef ( returnFiber , current , created , element ) ;
552
590
created . return = returnFiber ;
553
591
if ( __DEV__ ) {
554
592
created . _debugInfo = debugInfo ;
@@ -652,7 +690,7 @@ function createChildReconciler(
652
690
returnFiber . mode ,
653
691
lanes ,
654
692
) ;
655
- created . ref = coerceRef ( returnFiber , null , newChild ) ;
693
+ coerceRef ( returnFiber , null , created , newChild ) ;
656
694
created . return = returnFiber ;
657
695
if ( __DEV__ ) {
658
696
created . _debugInfo = mergeDebugInfo ( debugInfo , newChild . _debugInfo ) ;
@@ -1481,7 +1519,7 @@ function createChildReconciler(
1481
1519
) {
1482
1520
deleteRemainingChildren ( returnFiber , child . sibling ) ;
1483
1521
const existing = useFiber ( child , element . props ) ;
1484
- existing . ref = coerceRef ( returnFiber , child , element ) ;
1522
+ coerceRef ( returnFiber , child , existing , element ) ;
1485
1523
existing . return = returnFiber ;
1486
1524
if ( __DEV__ ) {
1487
1525
existing . _debugOwner = element . _owner ;
@@ -1513,7 +1551,7 @@ function createChildReconciler(
1513
1551
return created ;
1514
1552
} else {
1515
1553
const created = createFiberFromElement ( element , returnFiber . mode , lanes ) ;
1516
- created . ref = coerceRef ( returnFiber , currentFirstChild , element ) ;
1554
+ coerceRef ( returnFiber , currentFirstChild , created , element ) ;
1517
1555
created . return = returnFiber ;
1518
1556
if ( __DEV__ ) {
1519
1557
created . _debugInfo = debugInfo ;
0 commit comments