useCallbackRef is a custom hook that stores a callback in a ref, providing a stable function reference that always calls the latest callback. This is useful for preventing unnecessary re-renders or effect re-executions when callbacks are passed as props or dependencies.
Installation
npm install @radix-ui/react-use-callback-ref
Function Signature
function useCallbackRef<T extends (...args: any[]) => any>(
callback: T | undefined
): T
Parameters
The callback function to be converted to a stable ref-based function. Can be undefined.
Return Value
A memoized function that always calls the latest version of the provided callback. The function reference remains stable across re-renders.
Usage
Basic Example
import { useCallbackRef } from '@radix-ui/react-use-callback-ref';
function Component({ onEvent }: { onEvent?: () => void }) {
const handleEvent = useCallbackRef(onEvent);
useEffect(() => {
// handleEvent is stable, so this effect won't re-run when onEvent changes
const subscription = subscribe(handleEvent);
return () => subscription.unsubscribe();
}, [handleEvent]); // Safe to include in dependencies
return <div>Component</div>;
}
Preventing Re-renders
import { useCallbackRef } from '@radix-ui/react-use-callback-ref';
function Parent() {
const [count, setCount] = useState(0);
// Without useCallbackRef, this would cause Child to re-render on every count change
const handleClick = useCallbackRef(() => {
console.log('Current count:', count);
});
return <Child onClick={handleClick} />;
}
const Child = React.memo(({ onClick }: { onClick: () => void }) => {
return <button onClick={onClick}>Click me</button>;
});
Event Handler with Latest Props
import { useCallbackRef } from '@radix-ui/react-use-callback-ref';
function EscapeHandler({ onEscape }: { onEscape?: () => void }) {
const handleEscape = useCallbackRef(onEscape);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
handleEscape?.(); // Always calls the latest onEscape
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [handleEscape]); // Effect only runs once
return null;
}
Type Definition
function useCallbackRef<T extends (...args: any[]) => any>(
callback: T | undefined
): T
Implementation Details
The hook works by:
- Storing the callback in a ref using
useRef
- Updating the ref value on every render using
useEffect
- Returning a memoized function that calls the current ref value
This ensures:
- The returned function reference never changes
- The callback always executes with the latest closure
- No unnecessary re-renders or effect re-executions
Notes
This hook is particularly useful when you need to pass callbacks to child components or effect dependencies while ensuring they don’t trigger unnecessary updates.
The implementation addresses React issue #19240 regarding callback refs and stale closures.