Skip to content

Instantly share code, notes, and snippets.

@KurtGokhan
Last active December 16, 2024 08:25
Show Gist options
  • Save KurtGokhan/9aafd8e83c9bc6a2946fe2dc7f2c1d19 to your computer and use it in GitHub Desktop.
Save KurtGokhan/9aafd8e83c9bc6a2946fe2dc7f2c1d19 to your computer and use it in GitHub Desktop.
useCombinedRefs - Old and new
/**
* 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);
}
/**
* 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);
}
@DeniseMcClellan
Copy link

DeniseMcClellan commented Dec 5, 2023

The 'use-combined-refs-new.ts' version anticipates the callback ref cleanup feature in React 19, which is yet to be released. The 'use-combined-refs-old.ts' version is tailored for React 18 and employs useRef for maintaining previous refs. Both are useful for combining multiple refs into a single ref.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment