1.0.3 โ€ข Published 11 months ago

@aniruddha1806/use-event-listener v1.0.3

Weekly downloads
-
License
MIT
Repository
-
Last release
11 months ago

React useEventListener Hook

A type-safe, versatile event listener hook for React applications with TypeScript support. Easily attach event listeners to any DOM element, window, or document with proper cleanup and type safety.

Installation

npm install @aniruddha1806/use-event-listener

Features

  • ๐ŸŽฏ TypeScript support with full type definitions for all event types
  • ๐ŸŒ Works with Window, Document, and any HTML Element
  • ๐Ÿ”„ Supports both direct elements and React refs
  • ๐Ÿงน Automatic cleanup on unmount to prevent memory leaks
  • โš ๏ธ Development mode warnings for common mistakes
  • ๐Ÿ›ก๏ธ Type-safe event handling with proper event types
  • ๐Ÿ“ฆ Tiny footprint with zero dependencies
  • ๐Ÿ” Preserves the original listener reference between renders

Quick Start

import { useRef } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';

function App() {
  // Listen for window events
  useEventListener(window, 'resize', () => {
    console.log('Window resized!');
  });
  
  // Listen for document events
  useEventListener(document, 'visibilitychange', () => {
    console.log('Tab visibility changed!');
  });
  
  // Listen for element events using a ref
  const buttonRef = useRef(null);
  useEventListener(buttonRef, 'click', () => {
    console.log('Button clicked!');
  });
  
  return (
    <div>
      <h1>useEventListener Example</h1>
      <button ref={buttonRef}>Click me</button>
    </div>
  );
}

API

Parameters

ParameterTypeDescription
targetWindow \| Document \| HTMLElement \| RefObject<HTMLElement> \| Element \| RefObject<Element>The event target
typestringThe event type (e.g., 'click', 'resize')
listener(event: Event) => voidThe event callback function
optionsboolean \| AddEventListenerOptionsOptional event listener options

Returns

void - This hook doesn't return anything.

Examples

Window Event Listeners

Listen for global window events:

import useEventListener from '@aniruddha1806/use-event-listener';

function WindowEvents() {
  // Track window size
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });
  
  // Update dimensions on window resize
  useEventListener(window, 'resize', () => {
    setWindowSize({
      width: window.innerWidth,
      height: window.innerHeight,
    });
  });
  
  // Track online status
  const [isOnline, setIsOnline] = useState(navigator.onLine);
  
  useEventListener(window, 'online', () => setIsOnline(true));
  useEventListener(window, 'offline', () => setIsOnline(false));
  
  return (
    <div>
      <h2>Window Size</h2>
      <p>Width: {windowSize.width}px</p>
      <p>Height: {windowSize.height}px</p>
      
      <h2>Network Status</h2>
      <p>You are {isOnline ? 'online' : 'offline'}</p>
    </div>
  );
}

Document Event Listeners

Listen for document-level events:

import { useState } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';

function KeyboardShortcuts() {
  const [lastKey, setLastKey] = useState('');
  const [isFullScreen, setIsFullScreen] = useState(false);
  
  // Track keyboard shortcuts
  useEventListener(document, 'keydown', (event) => {
    setLastKey(event.key);
    
    // Handle keyboard shortcuts
    if (event.key === 'F' && event.ctrlKey) {
      event.preventDefault();
      toggleFullScreen();
    }
  });
  
  // Track document visibility
  useEventListener(document, 'visibilitychange', () => {
    console.log('Document visibility:', document.visibilityState);
  });
  
  const toggleFullScreen = () => {
    if (!document.fullscreenElement) {
      document.documentElement.requestFullscreen();
      setIsFullScreen(true);
    } else {
      document.exitFullscreen();
      setIsFullScreen(false);
    }
  };
  
  return (
    <div>
      <h2>Keyboard Shortcuts</h2>
      <p>Last key pressed: {lastKey || 'None'}</p>
      <p>Press Ctrl+F to toggle fullscreen</p>
      <p>Fullscreen: {isFullScreen ? 'Yes' : 'No'}</p>
    </div>
  );
}

Element Event Listeners with Refs

Listen for events on specific DOM elements using refs:

import { useRef, useState } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';

function DragAndDrop() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [isDragging, setIsDragging] = useState(false);
  const elementRef = useRef(null);
  
  // Handle mouse down on the element
  useEventListener(elementRef, 'mousedown', (event) => {
    setIsDragging(true);
    // Prevent text selection during drag
    event.preventDefault();
  });
  
  // Handle mouse move for dragging
  useEventListener(document, 'mousemove', (event) => {
    if (isDragging) {
      setPosition({
        x: event.clientX - 50, // Adjust for element size
        y: event.clientY - 50,
      });
    }
  });
  
  // Handle mouse up to stop dragging
  useEventListener(document, 'mouseup', () => {
    setIsDragging(false);
  });
  
  return (
    <div>
      <h2>Drag and Drop</h2>
      <div
        ref={elementRef}
        style={{
          position: 'absolute',
          left: `${position.x}px`,
          top: `${position.y}px`,
          width: '100px',
          height: '100px',
          backgroundColor: isDragging ? 'lightcoral' : 'lightblue',
          cursor: 'grab',
          userSelect: 'none',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          borderRadius: '8px',
        }}
      >
        Drag me
      </div>
    </div>
  );
}

Form Input Events

Handle form input events:

import { useRef, useState } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';

function FormEvents() {
  const [inputValue, setInputValue] = useState('');
  const [isFocused, setIsFocused] = useState(false);
  const inputRef = useRef(null);
  
  // Handle input changes
  useEventListener(inputRef, 'input', (event) => {
    setInputValue(event.target.value);
  });
  
  // Handle focus events
  useEventListener(inputRef, 'focus', () => {
    setIsFocused(true);
  });
  
  // Handle blur events
  useEventListener(inputRef, 'blur', () => {
    setIsFocused(false);
  });
  
  return (
    <div>
      <h2>Form Input Events</h2>
      <input
        ref={inputRef}
        type="text"
        placeholder="Type something..."
        style={{
          padding: '8px',
          border: `2px solid ${isFocused ? 'blue' : 'gray'}`,
          borderRadius: '4px',
          outline: 'none',
        }}
      />
      <p>Current value: {inputValue}</p>
      <p>Input is {isFocused ? 'focused' : 'not focused'}</p>
    </div>
  );
}

Passive Event Listeners

Use passive event listeners for better scroll performance:

import { useRef } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';

function SmoothScroll() {
  const scrollContainerRef = useRef(null);
  
  // Use passive: true for better scroll performance
  useEventListener(
    scrollContainerRef,
    'scroll',
    (event) => {
      console.log('Scroll position:', event.target.scrollTop);
    },
    { passive: true }
  );
  
  return (
    <div
      ref={scrollContainerRef}
      style={{
        height: '300px',
        overflow: 'auto',
        border: '1px solid #ccc',
        padding: '16px',
      }}
    >
      <h2>Smooth Scrolling</h2>
      {Array.from({ length: 50 }).map((_, index) => (
        <p key={index}>Scroll content item {index + 1}</p>
      ))}
    </div>
  );
}

TypeScript Usage

The hook provides full TypeScript support with proper type inference:

import { useRef, useState } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';

interface MousePosition {
  x: number;
  y: number;
}

function TypedEventExample() {
  // Properly typed state
  const [mousePosition, setMousePosition] = useState<MousePosition>({ x: 0, y: 0 });
  const [clicks, setClicks] = useState<number>(0);
  
  // Ref with proper HTML element type
  const buttonRef = useRef<HTMLButtonElement>(null);
  
  // Window event with proper event type
  useEventListener(window, 'mousemove', (event: MouseEvent) => {
    setMousePosition({ x: event.clientX, y: event.clientY });
  });
  
  // Element event with proper event type
  useEventListener(buttonRef, 'click', (event: MouseEvent) => {
    // event is properly typed as MouseEvent
    setClicks(prev => prev + 1);
    console.log('Click position:', event.clientX, event.clientY);
  });
  
  // Document event with proper event type
  useEventListener(document, 'visibilitychange', (event: Event) => {
    console.log('Visibility changed:', document.visibilityState);
  });
  
  return (
    <div>
      <h2>TypeScript Event Handling</h2>
      <p>
        Mouse position: {mousePosition.x}, {mousePosition.y}
      </p>
      <button ref={buttonRef}>
        Click me ({clicks} clicks)
      </button>
    </div>
  );
}

Advanced Usage

Event Delegation

Implement event delegation pattern:

import { useRef } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';

function EventDelegation() {
  const listRef = useRef(null);
  
  // Handle clicks on any list item through event delegation
  useEventListener(listRef, 'click', (event) => {
    // Find the closest list item that was clicked
    const listItem = event.target.closest('li');
    if (listItem) {
      console.log('List item clicked:', listItem.dataset.id);
      // Highlight the clicked item
      listItem.style.backgroundColor = '#f0f0f0';
      setTimeout(() => {
        listItem.style.backgroundColor = '';
      }, 300);
    }
  });
  
  return (
    <div>
      <h2>Event Delegation</h2>
      <ul ref={listRef} style={{ listStyle: 'none', padding: 0 }}>
        {Array.from({ length: 10 }).map((_, index) => (
          <li
            key={index}
            data-id={index + 1}
            style={{
              padding: '8px 16px',
              margin: '4px 0',
              border: '1px solid #ddd',
              borderRadius: '4px',
              cursor: 'pointer',
              transition: 'background-color 0.2s',
            }}
          >
            Item {index + 1}
          </li>
        ))}
      </ul>
    </div>
  );
}

Conditional Event Listeners

Add or remove event listeners conditionally:

import { useState } from 'react';
import useEventListener from '@aniruddha1806/use-event-listener';

function ConditionalEvents() {
  const [isListening, setIsListening] = useState(false);
  const [events, setEvents] = useState([]);
  
  // Only add the event listener when isListening is true
  useEventListener(
    isListening ? window : null,
    'mousemove',
    (event) => {
      setEvents((prev) => [
        ...prev.slice(-4),
        { x: event.clientX, y: event.clientY, time: new Date().toLocaleTimeString() },
      ]);
    },
    { passive: true }
  );
  
  return (
    <div>
      <h2>Conditional Event Listener</h2>
      <button onClick={() => setIsListening(!isListening)}>
        {isListening ? 'Stop Listening' : 'Start Listening'}
      </button>
      <p>Status: {isListening ? 'Tracking mouse movement' : 'Not tracking'}</p>
      
      <h3>Recent Mouse Events:</h3>
      <ul>
        {events.map((event, index) => (
          <li key={index}>
            Position: ({event.x}, {event.y}) at {event.time}
          </li>
        ))}
      </ul>
    </div>
  );
}
1.0.3

11 months ago

1.0.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago