FluentUI Compat

Home > @cascadiacollections/fluentui-compat > useEventCallback

useEventCallback() function

Hook to create a stable event handler that always calls the latest version of the callback.

This hook solves the problem where event handlers need access to the latest props/state without causing unnecessary re-renders or re-registrations of event listeners.

**Key Features**: - **Stable reference**: The returned callback has a stable identity that never changes - **Fresh values**: Always executes the latest version of the callback with current props/state - **Performance optimized**: Prevents re-renders in child components that depend on the callback - **Type safe**: Full TypeScript support with generic argument and return types - **React 19 compatible**: Uses useLayoutEffect for synchronous updates

**When to use**: - Event handlers that depend on frequently changing props/state - Callbacks passed to memoized child components to prevent unnecessary re-renders - Event listeners attached to window, document, or long-lived DOM elements - Callbacks in useEffect dependencies that shouldn't trigger the effect on every change

**Implementation Details**: - Uses useLayoutEffect to ensure callback ref updates synchronously before paint - Leverages useConst for a truly stable wrapper function that never changes - Throws error if called during render to enforce proper React patterns - Minimal overhead with no additional dependencies or complex state management

Signature:

export declare function useEventCallback<Args extends unknown[], Return>(fn: (...args: Args) => Return): (...args: Args) => Return;

Parameters

Parameter

Type

Description

fn

(...args: Args) => Return

The callback function to wrap. Can access current props/state when invoked.

Returns:

(...args: Args) => Return

A stable callback that always invokes the latest version of fn.

Example 1

// Event handler with frequently changing state
function SearchComponent() {
  const [query, setQuery] = useState('');
  const [filters, setFilters] = useState({});

  // Without useEventCallback: handleSearch would change on every query/filters update
  // causing MemoizedResults to re-render unnecessarily
  const handleSearch = useEventCallback(() => {
    performSearch(query, filters);
  });

  return (
    <>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      <MemoizedResults onSearch={handleSearch} />
    </>
  );
}

Example 2

// Window event listener with stable reference
function ResizeComponent() {
  const [width, setWidth] = useState(0);

  const handleResize = useEventCallback(() => {
    setWidth(window.innerWidth);
  });

  useEffect(() => {
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, [handleResize]); // handleResize never changes, so effect only runs once

  return <div>Width: {width}px</div>;
}

Example 3

// Callback in useEffect dependencies
function DataFetcher({ userId }: { userId: string }) {
  const [data, setData] = useState(null);
  const [refreshCount, setRefreshCount] = useState(0);

  const fetchData = useEventCallback(async () => {
    const result = await api.fetchUser(userId);
    setData(result);
  });

  useEffect(() => {
    fetchData();
  }, [refreshCount]); // Only re-fetch when refreshCount changes, not when userId changes

  // userId changes are picked up by fetchData automatically
  return <div>{data?.name}</div>;
}

Example 4

// Generic type support
function GenericHandler<T>({ items, onSelect }: { items: T[], onSelect: (item: T) => void }) {
  const handleClick = useEventCallback((item: T, index: number) => {
    console.log('Selected', item, 'at index', index);
    onSelect(item);
  });

  return (
    <ul>
      {items.map((item, i) => (
        <li key={i} onClick={() => handleClick(item, i)}>
          {String(item)}
        </li>
      ))}
    </ul>
  );
}
  • Edit this page
In this article
Back to top FluentUI React complimentary components and utilities focused on render performance