@@ -149,128 +149,140 @@ function unwrapThenable<T>(thenable: Thenable<T>): T {
149
149
return trackUsedThenable ( thenableState , thenable , index ) ;
150
150
}
151
151
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
+
152
247
function coerceRef (
153
248
returnFiber : Fiber ,
154
249
current : Fiber | null ,
250
+ workInProgress : Fiber ,
155
251
element : ReactElement ,
156
- ) {
252
+ ) : void {
157
253
let mixedRef ;
158
254
if ( enableRefAsProp ) {
159
255
// TODO: This is a temporary, intermediate step. When enableRefAsProp is on,
160
256
// we should resolve the `ref` prop during the begin phase of the component
161
257
// it's attached to (HostComponent, ClassComponent, etc).
162
-
163
258
const refProp = element . props . ref ;
164
259
mixedRef = refProp !== undefined ? refProp : null ;
165
260
} else {
166
261
// Old behavior.
167
262
mixedRef = element . ref ;
168
263
}
169
264
265
+ let coercedRef ;
170
266
if (
171
267
mixedRef !== null &&
172
268
typeof mixedRef !== 'function' &&
173
269
typeof mixedRef !== 'object'
174
270
) {
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 ;
272
281
}
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 ;
274
286
}
275
287
276
288
function throwOnInvalidObjectType ( returnFiber : Fiber , newChild : Object ) {
@@ -537,7 +549,7 @@ function createChildReconciler(
537
549
) {
538
550
// Move based on index
539
551
const existing = useFiber ( current , element . props ) ;
540
- existing . ref = coerceRef ( returnFiber , current , element ) ;
552
+ coerceRef ( returnFiber , current , existing , element ) ;
541
553
existing . return = returnFiber ;
542
554
if ( __DEV__ ) {
543
555
existing . _debugOwner = element . _owner ;
@@ -548,7 +560,7 @@ function createChildReconciler(
548
560
}
549
561
// Insert
550
562
const created = createFiberFromElement ( element , returnFiber . mode , lanes ) ;
551
- created . ref = coerceRef ( returnFiber , current , element ) ;
563
+ coerceRef ( returnFiber , current , created , element ) ;
552
564
created . return = returnFiber ;
553
565
if ( __DEV__ ) {
554
566
created . _debugInfo = debugInfo ;
@@ -652,7 +664,7 @@ function createChildReconciler(
652
664
returnFiber . mode ,
653
665
lanes ,
654
666
) ;
655
- created . ref = coerceRef ( returnFiber , null , newChild ) ;
667
+ coerceRef ( returnFiber , null , created , newChild ) ;
656
668
created . return = returnFiber ;
657
669
if ( __DEV__ ) {
658
670
created . _debugInfo = mergeDebugInfo ( debugInfo , newChild . _debugInfo ) ;
@@ -1481,7 +1493,7 @@ function createChildReconciler(
1481
1493
) {
1482
1494
deleteRemainingChildren ( returnFiber , child . sibling ) ;
1483
1495
const existing = useFiber ( child , element . props ) ;
1484
- existing . ref = coerceRef ( returnFiber , child , element ) ;
1496
+ coerceRef ( returnFiber , child , existing , element ) ;
1485
1497
existing . return = returnFiber ;
1486
1498
if ( __DEV__ ) {
1487
1499
existing . _debugOwner = element . _owner ;
@@ -1513,7 +1525,7 @@ function createChildReconciler(
1513
1525
return created ;
1514
1526
} else {
1515
1527
const created = createFiberFromElement ( element , returnFiber . mode , lanes ) ;
1516
- created . ref = coerceRef ( returnFiber , currentFirstChild , element ) ;
1528
+ coerceRef ( returnFiber , currentFirstChild , created , element ) ;
1517
1529
created . return = returnFiber ;
1518
1530
if ( __DEV__ ) {
1519
1531
created . _debugInfo = debugInfo ;
0 commit comments