CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-aria--utils

Essential utility functions and React hooks for building accessible React Aria UI components

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

event-management.mddocs/

Event Management

Cross-platform event handling with automatic cleanup and stable function references for React components.

Capabilities

useEvent Hook

Attaches event listeners to elements referenced by ref with automatic cleanup.

/**
 * Attaches event listeners to elements referenced by ref
 * @param ref - RefObject pointing to target element  
 * @param event - Event type to listen for
 * @param handler - Event handler function (optional)
 * @param options - addEventListener options
 */
function useEvent<K extends keyof GlobalEventHandlersEventMap>(
  ref: RefObject<EventTarget | null>,
  event: K,
  handler?: (this: Document, ev: GlobalEventHandlersEventMap[K]) => any,
  options?: AddEventListenerOptions
): void;

Usage Examples:

import { useEvent } from "@react-aria/utils";
import { useRef } from "react";

function InteractiveElement() {
  const elementRef = useRef<HTMLDivElement>(null);
  
  // Attach multiple event listeners with automatic cleanup
  useEvent(elementRef, 'mouseenter', (e) => {
    console.log('Mouse entered', e.target);
  });
  
  useEvent(elementRef, 'mouseleave', (e) => {
    console.log('Mouse left', e.target);
  });
  
  useEvent(elementRef, 'keydown', (e) => {
    if (e.key === 'Enter') {
      console.log('Enter pressed');
    }
  }, { passive: false });
  
  return (
    <div ref={elementRef} tabIndex={0}>
      Hover me or press Enter
    </div>
  );
}

// Conditional event listeners
function ConditionalEvents({ isActive }) {
  const buttonRef = useRef<HTMLButtonElement>(null);
  
  // Handler only attached when isActive is true
  useEvent(
    buttonRef, 
    'click', 
    isActive ? (e) => console.log('Button clicked') : undefined
  );
  
  return (
    <button ref={buttonRef}>
      {isActive ? 'Active Button' : 'Inactive Button'}
    </button>
  );
}

useGlobalListeners Hook

Manages global event listeners with automatic cleanup and proper handling.

/**
 * Manages global event listeners with automatic cleanup
 * @returns Object with methods for managing global listeners
 */
function useGlobalListeners(): GlobalListeners;

interface GlobalListeners {
  addGlobalListener<K extends keyof DocumentEventMap>(
    el: EventTarget,
    type: K,
    listener: (this: Document, ev: DocumentEventMap[K]) => any,
    options?: AddEventListenerOptions | boolean
  ): void;
  
  removeGlobalListener<K extends keyof DocumentEventMap>(
    el: EventTarget,
    type: K,
    listener: (this: Document, ev: DocumentEventMap[K]) => any,
    options?: EventListenerOptions | boolean
  ): void;
  
  removeAllGlobalListeners(): void;
}

Usage Examples:

import { useGlobalListeners } from "@react-aria/utils";

function GlobalEventComponent() {
  const { addGlobalListener, removeGlobalListener } = useGlobalListeners();
  
  useEffect(() => {
    const handleGlobalClick = (e) => {
      console.log('Global click detected');
    };
    
    const handleGlobalKeyDown = (e) => {
      if (e.key === 'Escape') {
        console.log('Escape pressed globally');
      }
    };
    
    // Add global listeners
    addGlobalListener(document, 'click', handleGlobalClick);
    addGlobalListener(document, 'keydown', handleGlobalKeyDown);
    
    // Manual cleanup (automatic cleanup happens on unmount)
    return () => {
      removeGlobalListener(document, 'click', handleGlobalClick);
      removeGlobalListener(document, 'keydown', handleGlobalKeyDown);
    };
  }, [addGlobalListener, removeGlobalListener]);
  
  return <div>Component with global event listeners</div>;
}

// Modal overlay with outside click detection
function ModalOverlay({ isOpen, onClose, children }) {
  const { addGlobalListener, removeGlobalListener } = useGlobalListeners();
  const overlayRef = useRef<HTMLDivElement>(null);
  
  useEffect(() => {
    if (!isOpen) return;
    
    const handleOutsideClick = (e) => {
      if (overlayRef.current && !overlayRef.current.contains(e.target)) {
        onClose();
      }
    };
    
    const handleEscape = (e) => {
      if (e.key === 'Escape') {
        onClose();
      }
    };
    
    // Add listeners when modal opens
    addGlobalListener(document, 'mousedown', handleOutsideClick);
    addGlobalListener(document, 'keydown', handleEscape);
    
    return () => {
      removeGlobalListener(document, 'mousedown', handleOutsideClick);
      removeGlobalListener(document, 'keydown', handleEscape);
    };
  }, [isOpen, onClose, addGlobalListener, removeGlobalListener]);
  
  return isOpen ? (
    <div className="modal-backdrop">
      <div ref={overlayRef} className="modal-content">
        {children}
      </div>
    </div>
  ) : null;
}

useEffectEvent Hook

Creates a stable function reference that always calls the latest version, preventing unnecessary effect re-runs.

/**
 * Creates a stable function reference that always calls the latest version
 * @param fn - Function to wrap
 * @returns Stable function reference
 */
function useEffectEvent<T extends Function>(fn?: T): T;

Usage Examples:

import { useEffectEvent } from "@react-aria/utils";

function SearchComponent({ query, onResults }) {
  // Stable reference to callback that doesn't cause effect re-runs
  const handleResults = useEffectEvent(onResults);
  
  useEffect(() => {
    if (!query) return;
    
    const searchAPI = async () => {
      const results = await fetch(`/api/search?q=${query}`);
      const data = await results.json();
      
      // This won't cause the effect to re-run when onResults changes
      handleResults(data);
    };
    
    searchAPI();
  }, [query]); // Only re-run when query changes, not when onResults changes
  
  return <div>Searching for: {query}</div>;
}

// Event handler that accesses latest state without dependencies
function Timer() {
  const [count, setCount] = useState(0);
  const [isRunning, setIsRunning] = useState(false);
  
  // Stable reference that always accesses latest state
  const tick = useEffectEvent(() => {
    if (isRunning) {
      setCount(c => c + 1);
    }
  });
  
  useEffect(() => {
    const interval = setInterval(tick, 1000);
    return () => clearInterval(interval);
  }, []); // No dependencies needed because tick is stable
  
  return (
    <div>
      <div>Count: {count}</div>
      <button onClick={() => setIsRunning(!isRunning)}>
        {isRunning ? 'Stop' : 'Start'}
      </button>
    </div>
  );
}

Advanced Event Management Patterns

Complex event handling scenarios with multiple listeners and cleanup:

import { useGlobalListeners, useEffectEvent, useEvent } from "@react-aria/utils";

function AdvancedEventManager({ onAction, isActive }) {
  const elementRef = useRef<HTMLDivElement>(null);
  const { addGlobalListener, removeGlobalListener } = useGlobalListeners();
  
  // Stable callback reference
  const handleAction = useEffectEvent(onAction);
  
  // Local element events
  useEvent(elementRef, 'click', isActive ? (e) => {
    handleAction('local-click', e);
  } : undefined);
  
  useEvent(elementRef, 'keydown', (e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      handleAction('local-activate', e);
    }
  });
  
  // Global events with conditional handling
  useEffect(() => {
    if (!isActive) return;
    
    const handleGlobalKeydown = (e) => {
      // Handle global shortcuts
      if (e.ctrlKey && e.key === 'k') {
        e.preventDefault();
        handleAction('global-search', e);
      }
    };
    
    const handleGlobalWheel = (e) => {
      // Custom scroll handling
      if (e.deltaY > 0) {
        handleAction('scroll-down', e);
      } else {
        handleAction('scroll-up', e);
      }
    };
    
    addGlobalListener(document, 'keydown', handleGlobalKeydown);
    addGlobalListener(document, 'wheel', handleGlobalWheel, { passive: true });
    
    return () => {
      removeGlobalListener(document, 'keydown', handleGlobalKeydown);
      removeGlobalListener(document, 'wheel', handleGlobalWheel);
    };
  }, [isActive, addGlobalListener, removeGlobalListener, handleAction]);
  
  return (
    <div ref={elementRef} tabIndex={0}>
      Advanced Event Manager
    </div>
  );
}

// Drag and drop with event management
function DragDropZone({ onDrop }) {
  const zoneRef = useRef<HTMLDivElement>(null);
  const { addGlobalListener, removeGlobalListener } = useGlobalListeners();
  const [isDragging, setIsDragging] = useState(false);
  
  const handleDrop = useEffectEvent(onDrop);
  
  // Local drag events
  useEvent(zoneRef, 'dragover', (e) => {
    e.preventDefault();
    setIsDragging(true);
  });
  
  useEvent(zoneRef, 'dragleave', (e) => {
    if (!zoneRef.current?.contains(e.relatedTarget as Node)) {
      setIsDragging(false);
    }
  });
  
  useEvent(zoneRef, 'drop', (e) => {
    e.preventDefault();
    setIsDragging(false);
    handleDrop(e.dataTransfer?.files);
  });
  
  // Global drag cleanup
  useEffect(() => {
    if (!isDragging) return;
    
    const handleGlobalDragEnd = () => {
      setIsDragging(false);
    };
    
    addGlobalListener(document, 'dragend', handleGlobalDragEnd);
    return () => removeGlobalListener(document, 'dragend', handleGlobalDragEnd);
  }, [isDragging, addGlobalListener, removeGlobalListener]);
  
  return (
    <div 
      ref={zoneRef}
      className={isDragging ? 'drag-over' : ''}
    >
      Drop files here
    </div>
  );
}

Types

interface AddEventListenerOptions {
  capture?: boolean;
  once?: boolean;
  passive?: boolean;
  signal?: AbortSignal;
}

interface EventListenerOptions {
  capture?: boolean;
}

interface GlobalEventHandlersEventMap {
  click: MouseEvent;
  keydown: KeyboardEvent;
  keyup: KeyboardEvent;
  mousedown: MouseEvent;
  mouseup: MouseEvent;
  mousemove: MouseEvent;
  wheel: WheelEvent;
  // ... other global event types
}

interface DocumentEventMap extends GlobalEventHandlersEventMap {
  // Document-specific events
}

Install with Tessl CLI

npx tessl i tessl/npm-react-aria--utils

docs

animation-and-transitions.md

event-management.md

focus-and-accessibility.md

id-and-refs.md

index.md

links-and-navigation.md

miscellaneous-utilities.md

platform-detection.md

props-and-events.md

scrolling-and-layout.md

shadow-dom-support.md

state-and-effects.md

virtual-events-and-input.md

tile.json