useStableCallback
A hook that provides a stable callback reference for handling unstable function props.
Source Code
import { useEffect, useRef, useCallback } from "react";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyFunction = (...args: any[]) => any;
export const useStableCallback = <T extends AnyFunction>(callback?: T) => {
const callbackRef = useRef<T | undefined>(callback);
// Update the ref if the callback changes
useEffect(() => {
if (callback) {
callbackRef.current = callback;
}
}, [callback]);
// Return a stable function that always calls the latest callback if it exists
const stableCallback = useCallback((...args: Parameters<T>) => {
if (callbackRef.current) {
callbackRef.current(...args);
}
}, []);
return stableCallback;
};
API Reference
Prop | Default | Type | Description |
---|---|---|---|
| - |
| The function to stabilize. |
The hook returns a stable function that will always call the latest version of the provided callback.
Examples
Handling Function Props
When receiving function props from a parent component, you often can't guarantee that the function is stable (wrapped in useCallback). This can lead to unnecessary re-renders or effects being triggered:
// This effect will re-run on every render because someFunc is unstable
const MyComponent = ({ someFunc }) => {
useEffect(() => {
// something that requires the func
}, [someFunc]);
return <button onClick={someFunc}>Click me</button>;
};
// Parent component passing an unstable function
const Parent = () => {
return <MyComponent someFunc={() => console.log("hello")} />;
};
Using useStableCallback
, you can stabilize the function prop:
const MyComponent = ({ someFunc }) => {
// Create a stable version of someFunc
const stableFunc = useStableCallback(someFunc);
useEffect(() => {
// This effect will only run once
}, [stableFunc]); // stableFunc is now stable
return <button onClick={stableFunc}>Click me</button>;
};
Event Handlers
Perfect for event handlers that need access to the latest state but should maintain a stable reference:
const SearchInput = ({ onSearch }) => {
const [query, setQuery] = useState("");
// Creates a stable callback that always has access to latest query
const handleSearch = useStableCallback(() => {
onSearch(query);
});
// The effect only runs once since handleSearch is stable
useEffect(() => {
const handler = debounce(handleSearch, 500);
return () => handler.cancel();
}, [handleSearch]);
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") handleSearch();
}}
/>
);
};