1
1
'use client' ;
2
2
import * as React from 'react' ;
3
- import setRef from '../setRef' ;
4
3
5
4
/**
6
- * Takes an array of refs and returns a new ref which will apply any modification to all of the refs.
7
- * This is useful when you want to have the ref used in multiple places.
5
+ * Merges refs into a single memoized callback ref or `null`.
8
6
*
9
7
* ```tsx
10
8
* const rootRef = React.useRef<Instance>(null);
@@ -21,20 +19,50 @@ import setRef from '../setRef';
21
19
export default function useForkRef < Instance > (
22
20
...refs : Array < React . Ref < Instance > | undefined >
23
21
) : React . RefCallback < Instance > | null {
24
- /**
25
- * This will create a new function if the refs passed to this hook change and are all defined.
26
- * This means react will call the old forkRef with `null` and the new forkRef
27
- * with the ref. Cleanup naturally emerges from this behavior.
28
- */
22
+ const cleanupRef = React . useRef < void | ( ( ) => void ) > ( undefined ) ;
23
+
24
+ const refEffect = React . useCallback ( ( instance : Instance | null ) => {
25
+ const cleanups = refs . map ( ( ref ) => {
26
+ if ( ref == null ) {
27
+ return null ;
28
+ }
29
+
30
+ if ( typeof ref === 'function' ) {
31
+ const refCallback = ref ;
32
+ const refCleanup : void | ( ( ) => void ) = refCallback ( instance ) ;
33
+ return typeof refCleanup === 'function'
34
+ ? refCleanup
35
+ : ( ) => {
36
+ refCallback ( null ) ;
37
+ } ;
38
+ }
39
+
40
+ ( ref as React . RefObject < Instance | null > ) . current = instance ;
41
+ return ( ) => {
42
+ ( ref as React . RefObject < Instance | null > ) . current = null ;
43
+ } ;
44
+ } ) ;
45
+
46
+ return ( ) => {
47
+ cleanups . forEach ( ( refCleanup ) => refCleanup ?.( ) ) ;
48
+ } ;
49
+ // eslint-disable-next-line react-hooks/exhaustive-deps
50
+ } , refs ) ;
51
+
29
52
return React . useMemo ( ( ) => {
30
53
if ( refs . every ( ( ref ) => ref == null ) ) {
31
54
return null ;
32
55
}
33
56
34
- return ( instance ) => {
35
- refs . forEach ( ( ref ) => {
36
- setRef ( ref , instance ) ;
37
- } ) ;
57
+ return ( value ) => {
58
+ if ( cleanupRef . current ) {
59
+ cleanupRef . current ( ) ;
60
+ ( cleanupRef as React . RefObject < void | ( ( ) => void ) > ) . current = undefined ;
61
+ }
62
+
63
+ if ( value != null ) {
64
+ ( cleanupRef as React . RefObject < void | ( ( ) => void ) > ) . current = refEffect ( value ) ;
65
+ }
38
66
} ;
39
67
// TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler -- intentionally ignoring that the dependency array must be an array literal
40
68
// eslint-disable-next-line react-hooks/exhaustive-deps
0 commit comments