Overview
composeRefs is a utility function that combines multiple React refs (both callback refs and ref objects) into a single callback ref. This is useful when you need to attach multiple refs to the same DOM element.
Import
import { composeRefs } from "@zayne-labs/toolkit-react";
Signature
function composeRefs<TRef extends HTMLElement>(
...refs: Array<PossibleRef<TRef>>
): RefCallback<TRef>
Parameters
...refs
Array<PossibleRef<TRef>>
required
A variable number of refs to combine. Each ref can be:
- A callback ref:
(node: TRef | null) => void
- A ref object:
React.RefObject<TRef>
undefined
Return Value
A callback ref that updates all provided refs when called. The callback also returns a cleanup function that is called when the ref is detached.
Types
type PossibleRef<TRef extends HTMLElement> = React.Ref<TRef> | undefined;
type RefCallback<TRef> = (node: TRef | null) => (() => void) | void;
Helper Function
setRef
setRef is a helper function used internally by composeRefs to set a single ref value:
function setRef<TRef extends HTMLElement>(
ref: PossibleRef<TRef>,
node: TRef | null
): ReturnType<RefCallback<TRef>>
This utility handles both callback refs and ref objects automatically.
Examples
Combining Local and Forwarded Refs
import { composeRefs } from "@zayne-labs/toolkit-react";
import { useRef, forwardRef } from "react";
const Input = forwardRef<HTMLInputElement, InputProps>((props, forwardedRef) => {
const localRef = useRef<HTMLInputElement>(null);
const handleFocus = () => {
if (localRef.current) {
localRef.current.select();
}
};
return (
<input
ref={composeRefs(localRef, forwardedRef)}
onFocus={handleFocus}
{...props}
/>
);
});
Multiple Callback Refs
import { composeRefs } from "@zayne-labs/toolkit-react";
import { useCallback } from "react";
function Component() {
const measureRef = useCallback((node: HTMLDivElement | null) => {
if (node) {
console.log("Width:", node.offsetWidth);
}
}, []);
const observeRef = useCallback((node: HTMLDivElement | null) => {
if (node) {
const observer = new ResizeObserver((entries) => {
console.log("Resized:", entries[0].contentRect);
});
observer.observe(node);
// Return cleanup function
return () => observer.disconnect();
}
}, []);
return <div ref={composeRefs(measureRef, observeRef)}>Content</div>;
}
With useImperativeHandle
import { composeRefs } from "@zayne-labs/toolkit-react";
import { useRef, useImperativeHandle, forwardRef } from "react";
type VideoPlayerRef = {
play: () => void;
pause: () => void;
};
const VideoPlayer = forwardRef<VideoPlayerRef, VideoPlayerProps>((props, ref) => {
const videoRef = useRef<HTMLVideoElement>(null);
useImperativeHandle(ref, () => ({
play: () => videoRef.current?.play(),
pause: () => videoRef.current?.pause(),
}));
const trackRef = useCallback((node: HTMLVideoElement | null) => {
if (node) {
console.log("Video element mounted");
}
}, []);
return <video ref={composeRefs(videoRef, trackRef)} {...props} />;
});
Handling Undefined Refs
import { composeRefs } from "@zayne-labs/toolkit-react";
function Button({ forwardedRef }: { forwardedRef?: React.Ref<HTMLButtonElement> }) {
const localRef = useRef<HTMLButtonElement>(null);
// composeRefs handles undefined refs gracefully
return (
<button ref={composeRefs(localRef, forwardedRef)}>
Click me
</button>
);
}
With Cleanup Functions
import { composeRefs } from "@zayne-labs/toolkit-react";
import { useCallback } from "react";
function ScrollTracker() {
const scrollRef = useCallback((node: HTMLDivElement | null) => {
if (node) {
const handleScroll = () => {
console.log("Scrolled:", node.scrollTop);
};
node.addEventListener("scroll", handleScroll);
// Cleanup function
return () => {
node.removeEventListener("scroll", handleScroll);
};
}
}, []);
const focusRef = useCallback((node: HTMLDivElement | null) => {
if (node) {
const handleFocus = () => console.log("Focused");
const handleBlur = () => console.log("Blurred");
node.addEventListener("focus", handleFocus);
node.addEventListener("blur", handleBlur);
// Cleanup function
return () => {
node.removeEventListener("focus", handleFocus);
node.removeEventListener("blur", handleBlur);
};
}
}, []);
return <div ref={composeRefs(scrollRef, focusRef)} tabIndex={0}>Tracked Element</div>;
}
How It Works
- Combines refs: Takes multiple refs and creates a single callback ref
- Sets all refs: When the callback is invoked with a node, it updates all provided refs
- Handles cleanup: Collects cleanup functions from callback refs and executes them when the ref is detached
- Type-safe: Properly typed for TypeScript usage
Best Practices
- Use with forwardRef: Combine forwarded refs with local refs for component composition
- Return cleanup functions: If your callback refs set up side effects, return cleanup functions
- Handle null refs: The utility gracefully handles undefined refs, so you don’t need to check
- Type your refs: Always specify the HTML element type for better type safety