Last active
June 6, 2025 10:24
-
-
Save KurtGokhan/9aafd8e83c9bc6a2946fe2dc7f2c1d19 to your computer and use it in GitHub Desktop.
useCombinedRefs - Old and new
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A combined ref implementation using the callback ref cleanups feature. | |
* This will work in React 19. | |
*/ | |
import { Ref, useCallback } from 'react'; | |
type OptionalRef<T> = Ref<T> | undefined; | |
type Cleanup = (() => void) | undefined | void; | |
function setRef<T>(ref: OptionalRef<T>, value: T): Cleanup { | |
if (typeof ref === 'function') { | |
const cleanup = ref(value); | |
if (typeof cleanup === 'function') { | |
return cleanup; | |
} | |
return () => ref(null); | |
} else if (ref) { | |
ref.current = value; | |
return () => (ref.current = null); | |
} | |
} | |
export function useCombinedRefs<T>(...refs: OptionalRef<T>[]) { | |
// biome-ignore lint/correctness/useExhaustiveDependencies: The hook already lists all dependencies | |
return useCallback((value: T | null) => { | |
const cleanups: Cleanup[] = []; | |
for (const ref of refs) { | |
const cleanup = setRef(ref, value); | |
cleanups.push(cleanup); | |
} | |
return () => { | |
for (const cleanup of cleanups) { | |
cleanup?.(); | |
} | |
}; | |
// eslint-disable-next-line react-hooks/exhaustive-deps | |
}, refs); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A combined ref implementation that will work in React 18 | |
*/ | |
import { ForwardedRef, useCallback, useRef } from 'react'; | |
type OptionalRef<T> = ForwardedRef<T> | undefined; | |
function setRef<T>(ref: OptionalRef<T>, value: T) { | |
if (typeof ref === 'function') { | |
ref(value); | |
} else if (ref) { | |
ref.current = value; | |
} | |
} | |
export function useCombinedRefs<T>(...refs: OptionalRef<T>[]) { | |
const previousRefs = useRef<OptionalRef<T>[]>([]); | |
return useCallback((value: T | null) => { | |
let index = 0; | |
for (; index < refs.length; index++) { | |
const ref = refs[index]; | |
const prev = previousRefs.current[index]; | |
// eslint-disable-next-line eqeqeq | |
if (prev != ref) setRef(prev, null); | |
setRef(ref, value); | |
} | |
for (; index < previousRefs.current.length; index++) { | |
const prev = previousRefs.current[index]; | |
setRef(prev, null); | |
} | |
previousRefs.current = refs; | |
// eslint-disable-next-line react-hooks/exhaustive-deps | |
}, refs); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I first assumed the "new" one supported old and new React, but this is not the case. Still useful for app developers but if you're building a library where the React version can be anything a more solid implementation is something like https://github.com/vercel/next.js/blob/canary/packages/next/src/client/use-merged-ref.ts