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

PropDefaultTypeDescription

callback

-

T extends (...args: any[]) => any

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();
      }}
    />
  );
};