useCallbackRef
Return a stable callback that always calls the latest function, avoiding unnecessary re-renders and effect re-runs.
useCallbackRef
useCallbackRef creates a stable function whose identity never changes, but whose internal reference updates to the latest callback you pass in. This is useful when you want to:
- Avoid re-running effects that depend on a callback
- Pass handlers down as props without changing identity every render
- Subscribe/unsubscribe to DOM events or third-party APIs once, while still using the freshest logic
Unlike useCallback, which memoizes based on a dependency list, useCallbackRef returns a single stable function and swaps the underlying implementation via a ref.
API
function useCallbackRef<T extends (...args: any[]) => any>(
callback: T | undefined,
): T;Prop
Type
Return value:
- A stable function (identity does not change between renders) that, when invoked, calls the latest
callbackyou provided. Ifcallbackisundefined, invoking the stable function is a no-op.
Usage
1) Prevent effect re-runs caused by callback identity
import * as React from 'react';
import { useCallbackRef } from '@loke/ui/use-callback-ref';
export function Timer({ onTick }: { onTick?: (n: number) => void }) {
const [count, setCount] = React.useState(0);
// Create a stable wrapper that always calls the latest onTick
const onTickRef = useCallbackRef(onTick);
React.useEffect(() => {
const id = setInterval(() => {
setCount((n) => {
const next = n + 1;
onTickRef?.(next);
return next;
});
}, 1000);
return () => clearInterval(id);
}, [onTickRef]); // won't change identity every render
return <div className="text-sm text-muted-foreground">Count: {count}</div>;
}Without useCallbackRef, the interval effect would need to re-subscribe any time onTick changes identity.
2) Stable event subscriptions (DOM / third-party APIs)
import * as React from 'react';
import { useCallbackRef } from '@loke/ui/use-callback-ref';
export function GlobalKeyLogger({ onKey }: { onKey?: (e: KeyboardEvent) => void }) {
const handleKey = useCallbackRef(onKey);
React.useEffect(() => {
function listener(e: KeyboardEvent) {
handleKey?.(e);
}
window.addEventListener('keydown', listener);
return () => window.removeEventListener('keydown', listener);
}, [handleKey]);
return null;
}Here, we add the event listener once and always invoke the latest onKey.
3) Passing handlers down without forcing memoization elsewhere
import * as React from 'react';
import { useCallbackRef } from '@loke/ui/use-callback-ref';
function ToolbarButton({
onPress,
children,
}: {
onPress?: (e: React.MouseEvent<HTMLButtonElement>) => void;
children: React.ReactNode;
}) {
const onPressRef = useCallbackRef(onPress);
return (
<button
type="button"
// Stable identity; downstream memoized children won't re-render due to handler identity
onClick={(e) => onPressRef?.(e)}
className="inline-flex items-center rounded-md border px-3 py-2"
>
{children}
</button>
);
}How it works
- Internally stores your latest
callbackin a ref - Returns a single function created with
useMemo(empty dependency array) - When invoked, that stable function calls
callbackRef.current?.(...args) - Because the wrapper identity is stable, you can safely:
- Put it into dependency arrays for effects
- Pass it down as a prop without causing child re-renders
If you pass undefined as the callback, the returned function is still stable, but calling it is a no-op.
Patterns and tips
- Use for long-lived subscriptions (DOM events, WebSocket, timers) where you want mount/unmount only once.
- Ideal when handing a callback to a memoized child that shouldn’t re-render every parent render.
- If you truly need a new function identity when dependencies change, use useCallback instead.
Edge cases
- Return type: The returned function type matches your input type
T. Ifcallbackis optional, the returned function can be invoked safely; it no-ops when the underlying callback isundefined. thisbinding: The returned function is a plain function; do not rely onthis. Prefer lexical binding or arrow functions.- SSR: The hook uses
useEffectinternally; it’s safe for SSR – the effect simply attaches the latest callback after hydration.
Import
import { useCallbackRef } from '@loke/ui/use-callback-ref';useControllableState
A small hook for building components that support both controlled and uncontrolled usage with one consistent API.
useIsDocumentHidden
Track whether the current document is hidden or visible using the Page Visibility API — useful for pausing timers or deprioritizing work when the tab is backgrounded.