Essential utility functions and React hooks for building accessible React Aria UI components
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Cross-platform event handling with automatic cleanup and stable function references for React components.
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>
);
}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;
}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>
);
}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>
);
}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