or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

development-tools.mdevent-handling.mdindex.mdlifecycle-management.mdreference-management.mdstate-management.mdtiming-utilities.mdutility-hooks.md
tile.json

event-handling.mddocs/

Event Handling

Hooks for creating stable event callbacks and managing DOM event listeners with automatic cleanup.

Capabilities

useEventCallback

Modified useCallback that returns the same function reference every time, but internally calls the most-recently passed callback implementation. This solves common issues with event handler dependencies that change too frequently.

/**
 * Modified useCallback that returns the same function reference every time, but internally calls
 * the most-recently passed callback implementation. Can be useful in situations such as:
 * - Event handler dependencies change too frequently, such as user props which might change on every render
 * - Callback must be referenced in a captured context but needs access to the latest props
 * 
 * In general, prefer useCallback unless you've encountered one of the problems above.
 * @param fn - The callback function that will be used
 * @returns A function which is referentially stable but internally calls the most recently passed callback
 */
function useEventCallback<Args extends unknown[], Return>(
  fn: (...args: Args) => Return
): (...args: Args) => Return;

Usage Examples:

import { useEventCallback } from "@fluentui/react-hooks";

function SearchComponent({ onSearch, searchTerm, filters }) {
  // ❌ Problem: useCallback depends on frequently changing values
  const handleSearch = useCallback(() => {
    onSearch(searchTerm, filters);
  }, [onSearch, searchTerm, filters]); // Dependencies change on every render

  // ✅ Solution: useEventCallback always has stable reference
  const handleSearch = useEventCallback(() => {
    onSearch(searchTerm, filters); // Always uses latest values
  });

  return <button onClick={handleSearch}>Search</button>;
}

// Perfect for window event listeners
function WindowEventComponent() {
  const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });

  const handleResize = useEventCallback(() => {
    setWindowSize({
      width: window.innerWidth,
      height: window.innerHeight
    });
  });

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

  return <div>Window size: {windowSize.width} x {windowSize.height}</div>;
}

useOnEvent

Hook to attach an event handler on mount and handle cleanup automatically. Provides a declarative way to manage DOM event listeners.

/**
 * Hook to attach an event handler on mount and handle cleanup.
 * @param element - Element (or ref to an element) to attach the event handler to
 * @param eventName - The event to attach a handler for
 * @param callback - The handler for the event
 * @param useCapture - Whether or not to attach the handler for the capture phase
 */
function useOnEvent<TElement extends Element, TEvent extends Event>(
  element: React.RefObject<TElement | undefined | null> | TElement | Window | Document | undefined | null,
  eventName: string,
  callback: (ev: TEvent) => void,
  useCapture?: boolean
): void;

Usage Examples:

import { useOnEvent } from "@fluentui/react-hooks";

function ClickOutsideComponent() {
  const containerRef = useRef<HTMLDivElement>(null);
  const [isOpen, setIsOpen] = useState(false);

  // Close dropdown when clicking outside
  useOnEvent(
    document,
    'click',
    (event: MouseEvent) => {
      if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
        setIsOpen(false);
      }
    }
  );

  return (
    <div ref={containerRef}>
      <button onClick={() => setIsOpen(true)}>Open Dropdown</button>
      {isOpen && (
        <div className="dropdown">
          <div>Dropdown content</div>
        </div>
      )}
    </div>
  );
}

// Keyboard event handling
function KeyboardNavigationComponent() {
  const [currentIndex, setCurrentIndex] = useState(0);
  const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4'];

  useOnEvent(
    window,
    'keydown',
    (event: KeyboardEvent) => {
      switch (event.key) {
        case 'ArrowUp':
          event.preventDefault();
          setCurrentIndex(prev => Math.max(0, prev - 1));
          break;
        case 'ArrowDown':
          event.preventDefault();
          setCurrentIndex(prev => Math.min(items.length - 1, prev + 1));
          break;
      }
    }
  );

  return (
    <ul>
      {items.map((item, index) => (
        <li 
          key={index}
          style={{ 
            backgroundColor: index === currentIndex ? '#blue' : 'transparent' 
          }}
        >
          {item}
        </li>
      ))}
    </ul>
  );
}

// Event handling on specific element refs
function ElementEventComponent() {
  const buttonRef = useRef<HTMLButtonElement>(null);
  const [clickCount, setClickCount] = useState(0);

  // Handle events directly on the button element
  useOnEvent(
    buttonRef,
    'click',
    (event: MouseEvent) => {
      setClickCount(prev => prev + 1);
      console.log('Button clicked!', event);
    }
  );

  // Handle mouse enter/leave
  useOnEvent(
    buttonRef,
    'mouseenter',
    () => console.log('Mouse entered button')
  );

  useOnEvent(
    buttonRef,
    'mouseleave',
    () => console.log('Mouse left button')
  );

  return (
    <div>
      <button ref={buttonRef}>
        Click me! (Clicked {clickCount} times)
      </button>
    </div>
  );
}