Octocat

useStableCallback

A hook that provides a stable callback reference for handling unstable function props.

Source Code

import { useCallback, useEffect, useRef } from 'react';

// biome-ignore lint/suspicious/noExplicitAny: expected
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
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();
      }}
    />
  );
};
        

Previous

useScrollLock

Next

useTailwindBreakpoint