Event Handling Implementation Patterns

1. React SyntheticEvent preventDefault

Feature Syntax Description Use Case
SyntheticEvent event: React.MouseEvent<T> Cross-browser wrapper around native events Consistent event handling
preventDefault event.preventDefault() Prevents default browser action Form submission, link navigation
stopPropagation event.stopPropagation() Stops event bubbling to parent elements Nested clickable elements
currentTarget event.currentTarget Element handler is attached to Event delegation
target event.target Element that triggered the event Dynamic element access
nativeEvent event.nativeEvent Access underlying browser event Browser-specific features

Example: React SyntheticEvent with type-safe handlers

import React from 'react';

// Form submission with preventDefault
function LoginForm() {
  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault(); // Prevent page reload
    
    const formData = new FormData(event.currentTarget);
    const email = formData.get('email') as string;
    const password = formData.get('password') as string;
    
    console.log({ email, password });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      <button type="submit">Login</button>
    </form>
  );
}

// Click event with stopPropagation
function NestedButtons() {
  const handleParentClick = () => {
    console.log('Parent clicked');
  };

  const handleChildClick = (event: React.MouseEvent<HTMLButtonElement>) => {
    event.stopPropagation(); // Prevent parent handler from firing
    console.log('Child clicked');
  };

  return (
    <div onClick={handleParentClick} style={{ padding: '20px', background: '#eee' }}>
      <button onClick={handleChildClick}>
        Click me (won't trigger parent)
      </button>
    </div>
  );
}

// Input change with proper typing
function SearchInput() {
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const value = event.target.value;
    console.log('Search:', value);
  };

  const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
    if (event.key === 'Enter') {
      event.preventDefault();
      console.log('Search submitted');
    }
  };

  return (
    <input
      type="text"
      onChange={handleChange}
      onKeyPress={handleKeyPress}
      placeholder="Search..."
    />
  );
}

// Mouse events with coordinates
function TrackMouse() {
  const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
    const { clientX, clientY, pageX, pageY } = event;
    console.log('Client:', clientX, clientY);
    console.log('Page:', pageX, pageY);
  };

  const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
    // Access native event for browser-specific features
    console.log('Native event:', event.nativeEvent);
    console.log('Button clicked:', event.button); // 0=left, 1=middle, 2=right
  };

  return (
    <div 
      onMouseMove={handleMouseMove}
      onClick={handleClick}
      style={{ width: '300px', height: '300px', background: '#f0f0f0' }}
    >
      Move mouse here
    </div>
  );
}

// Focus events
function FocusExample() {
  const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
    event.target.style.borderColor = 'blue';
  };

  const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
    event.target.style.borderColor = 'gray';
  };

  return (
    <input
      type="text"
      onFocus={handleFocus}
      onBlur={handleBlur}
      placeholder="Focus me"
    />
  );
}

2. Event Delegation querySelector addEventListener

Method Syntax Description Use Case
addEventListener element.addEventListener('click', handler) Attaches event listener to element DOM event handling
removeEventListener element.removeEventListener('click', handler) Removes specific event listener Cleanup, memory management
Event Delegation parent.addEventListener('click', (e) => {}) Single listener on parent handles child events Dynamic elements, performance
event.target e.target.matches('.class') Identifies which child triggered event Delegation filtering
capture phase addEventListener('click', fn, true) Event fires during capture before bubble Event interception
once option addEventListener('click', fn, { once: true }) Listener auto-removes after first call One-time events

Example: Event delegation with dynamic elements

// Event delegation for dynamic list items
function setupDynamicList() {
  const list = document.querySelector('#todo-list');

  // Single event listener on parent handles all children
  list.addEventListener('click', (event) => {
    const target = event.target;

    // Check if clicked element matches selector
    if (target.matches('.delete-btn')) {
      const todoItem = target.closest('.todo-item');
      todoItem.remove();
    }

    if (target.matches('.checkbox')) {
      const todoItem = target.closest('.todo-item');
      todoItem.classList.toggle('completed');
    }

    if (target.matches('.edit-btn')) {
      const todoItem = target.closest('.todo-item');
      editTodo(todoItem);
    }
  });

  // Add new items dynamically - no need for new listeners
  function addTodo(text) {
    const li = document.createElement('li');
    li.className = 'todo-item';
    li.innerHTML = `
      <input type="checkbox" class="checkbox">
      <span>${text}</span>
      <button class="edit-btn">Edit</button>
      <button class="delete-btn">Delete</button>
    `;
    list.appendChild(li);
  }
}

// Event listener options
function advancedEventListening() {
  const button = document.querySelector('#submit-btn');

  // Once option - auto-removes after first call
  button.addEventListener('click', handleClick, { once: true });

  // Passive option - improves scroll performance
  document.addEventListener('scroll', handleScroll, { passive: true });

  // Capture phase - fires before bubble phase
  document.addEventListener('click', captureHandler, true);

  function handleClick(event) {
    console.log('Button clicked once');
  }

  function handleScroll(event) {
    // Cannot call preventDefault in passive listener
    console.log('Scrolling...');
  }

  function captureHandler(event) {
    console.log('Capture phase:', event.target);
  }
}

// React hook for native event listeners
import { useEffect, useRef } from 'react';

function useEventListener(
  eventName: string,
  handler: (event: Event) => void,
  element: HTMLElement | Window = window
) {
  const savedHandler = useRef<(event: Event) => void>();

  useEffect(() => {
    savedHandler.current = handler;
  }, [handler]);

  useEffect(() => {
    const isSupported = element && element.addEventListener;
    if (!isSupported) return;

    const eventListener = (event: Event) => savedHandler.current?.(event);
    element.addEventListener(eventName, eventListener);

    return () => {
      element.removeEventListener(eventName, eventListener);
    };
  }, [eventName, element]);
}

// Usage in React component
function Component() {
  const divRef = useRef<HTMLDivElement>(null);

  useEventListener('mousemove', (event) => {
    console.log('Mouse position:', event.clientX, event.clientY);
  }, divRef.current);

  useEventListener('resize', () => {
    console.log('Window resized');
  }, window);

  return <div ref={divRef}>Content</div>;
}

// Event delegation with closest()
document.querySelector('.container').addEventListener('click', (event) => {
  // Find closest ancestor matching selector
  const card = event.target.closest('.card');
  if (card) {
    console.log('Card clicked:', card.dataset.id);
  }
});

3. Custom Events dispatch CustomEvent

API Syntax Description Use Case
CustomEvent new CustomEvent('type', { detail }) Creates custom event with data payload Component communication
detail { detail: { data: value } } Payload data attached to event Data passing
bubbles { bubbles: true } Event propagates up DOM tree Event bubbling
dispatchEvent element.dispatchEvent(event) Triggers custom event on element Event emission
composed { composed: true } Event crosses shadow DOM boundary Web Components

Example: Custom events for component communication

// Creating and dispatching custom events
class NotificationSystem {
  static show(message, type = 'info') {
    const event = new CustomEvent('app:notification', {
      detail: {
        message,
        type,
        timestamp: Date.now(),
      },
      bubbles: true,
      composed: true,
    });
    document.dispatchEvent(event);
  }
}

// Listening to custom events
document.addEventListener('app:notification', (event) => {
  const { message, type, timestamp } = event.detail;
  console.log(`[${type}] ${message} at ${new Date(timestamp)}`);
  showToast(message, type);
});

// Usage
NotificationSystem.show('User logged in', 'success');
NotificationSystem.show('Failed to load data', 'error');

// React hook for custom events
function useCustomEvent(eventName, handler) {
  useEffect(() => {
    const eventHandler = (event) => handler(event.detail);
    document.addEventListener(eventName, eventHandler);
    return () => document.removeEventListener(eventName, eventHandler);
  }, [eventName, handler]);
}

// Component emitting custom event
function CartButton() {
  const addToCart = (product) => {
    const event = new CustomEvent('cart:add', {
      detail: { product, quantity: 1 },
      bubbles: true,
    });
    document.dispatchEvent(event);
  };

  return <button onClick={() => addToCart({ id: 1, name: 'Product' })}>Add to Cart</button>;
}

// Component listening to custom event
function CartCounter() {
  const [count, setCount] = useState(0);

  useCustomEvent('cart:add', (detail) => {
    setCount(prev => prev + detail.quantity);
  });

  return <div>Cart: {count}</div>;
}

// Web Component with custom events
class UserCard extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <div class="card">
        <h3>${this.getAttribute('name')}</h3>
        <button id="follow-btn">Follow</button>
      </div>
    `;

    this.querySelector('#follow-btn').addEventListener('click', () => {
      const event = new CustomEvent('user:follow', {
        detail: {
          userId: this.getAttribute('user-id'),
          userName: this.getAttribute('name'),
        },
        bubbles: true,
        composed: true, // Cross shadow DOM
      });
      this.dispatchEvent(event);
    });
  }
}

customElements.define('user-card', UserCard);

// Listen to web component events
document.addEventListener('user:follow', (event) => {
  console.log('Following user:', event.detail.userName);
});

// TypeScript typed custom events
interface AppEventMap {
  'cart:add': CustomEvent<{ product: Product; quantity: number }>;
  'user:login': CustomEvent<{ user: User }>;
  'notification': CustomEvent<{ message: string; type: string }>;
}

function dispatchTypedEvent<K extends keyof AppEventMap>(
  type: K,
  detail: AppEventMap[K]['detail']
) {
  const event = new CustomEvent(type, { detail, bubbles: true });
  document.dispatchEvent(event);
}

// Type-safe usage
dispatchTypedEvent('cart:add', { product: myProduct, quantity: 2 });

4. Debounce Throttle Lodash useDebounce

Technique Behavior Description Use Case
Debounce Delays execution until pause in events Waits for event stream to stop, then executes once Search input, window resize
Throttle Limits execution rate to interval Executes at most once per time period Scroll, mouse move, animation
lodash.debounce debounce(fn, delay, options) Debounce with leading/trailing edge control Production-ready utility
lodash.throttle throttle(fn, interval, options) Throttle with leading/trailing edge control Rate limiting
useDebounce hook const debounced = useDebounce(value, delay) React hook for debounced values React state debouncing

Example: Debounce and throttle implementations with React hooks

// Pure JavaScript debounce implementation
function debounce(func, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
}

// Pure JavaScript throttle implementation
function throttle(func, interval) {
  let lastCall = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastCall >= interval) {
      lastCall = now;
      func.apply(this, args);
    }
  };
}

// Debounced search input
const searchInput = document.querySelector('#search');
const debouncedSearch = debounce((query) => {
  console.log('Searching for:', query);
  fetchResults(query);
}, 500);

searchInput.addEventListener('input', (e) => {
  debouncedSearch(e.target.value);
});

// Throttled scroll handler
const handleScroll = throttle(() => {
  console.log('Scroll position:', window.scrollY);
  updateScrollPosition();
}, 100);

window.addEventListener('scroll', handleScroll);

// React useDebounce hook
function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(handler);
  }, [value, delay]);

  return debouncedValue;
}

// React useThrottle hook
function useThrottle<T>(value: T, interval: number): T {
  const [throttledValue, setThrottledValue] = useState(value);
  const lastUpdated = useRef(Date.now());

  useEffect(() => {
    const now = Date.now();
    const timeSinceLastUpdate = now - lastUpdated.current;

    if (timeSinceLastUpdate >= interval) {
      lastUpdated.current = now;
      setThrottledValue(value);
    } else {
      const timeoutId = setTimeout(() => {
        lastUpdated.current = Date.now();
        setThrottledValue(value);
      }, interval - timeSinceLastUpdate);

      return () => clearTimeout(timeoutId);
    }
  }, [value, interval]);

  return throttledValue;
}

// Debounced search component
function SearchBox() {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  useEffect(() => {
    if (debouncedSearchTerm) {
      console.log('Searching:', debouncedSearchTerm);
      fetchSearchResults(debouncedSearchTerm);
    }
  }, [debouncedSearchTerm]);

  return (
    <input
      type="text"
      value={searchTerm}
      onChange={(e) => setSearchTerm(e.target.value)}
      placeholder="Search..."
    />
  );
}

// Throttled scroll component
function ScrollTracker() {
  const [scrollY, setScrollY] = useState(0);
  const throttledScrollY = useThrottle(scrollY, 100);

  useEffect(() => {
    const handleScroll = () => setScrollY(window.scrollY);
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  return <div>Scroll Position: {throttledScrollY}px</div>;
}

// Using lodash debounce/throttle
import { debounce, throttle } from 'lodash';

function SearchWithLodash() {
  const debouncedSearch = useMemo(
    () => debounce((query) => {
      console.log('Searching:', query);
    }, 500, {
      leading: false,
      trailing: true,
    }),
    []
  );

  useEffect(() => {
    return () => debouncedSearch.cancel(); // Cleanup
  }, [debouncedSearch]);

  return (
    <input
      onChange={(e) => debouncedSearch(e.target.value)}
      placeholder="Search..."
    />
  );
}

// Advanced: Debounce with abort controller
function useDebouncedCallback(callback, delay) {
  const callbackRef = useRef(callback);
  const timeoutRef = useRef();
  const abortControllerRef = useRef();

  useEffect(() => {
    callbackRef.current = callback;
  }, [callback]);

  return useCallback((...args) => {
    // Cancel previous request
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }

    clearTimeout(timeoutRef.current);

    timeoutRef.current = setTimeout(() => {
      abortControllerRef.current = new AbortController();
      callbackRef.current(...args, abortControllerRef.current.signal);
    }, delay);
  }, [delay]);
}

5. Keyboard Navigation Tab Index Focus

Property/Method Syntax Description Use Case
tabIndex tabIndex={0} Makes element keyboard focusable in DOM order Custom interactive elements
tabIndex -1 tabIndex={-1} Focusable programmatically but not via Tab Modal focus management
focus() element.focus() Programmatically sets focus to element Auto-focus input
onKeyDown onKeyDown={(e) => {}} Handles keyboard key press events Keyboard shortcuts
roving tabindex One item tabIndex={0}, others -1 Single Tab stop for component group Toolbars, menus, lists
aria-activedescendant aria-activedescendant="id" Indicates active descendant without focus move Listbox, combobox

Example: Keyboard navigation with roving tabindex

// Simple keyboard navigation
function KeyboardShortcuts() {
  const handleKeyDown = (event: React.KeyboardEvent) => {
    // Ctrl/Cmd + S = Save
    if ((event.ctrlKey || event.metaKey) && event.key === 's') {
      event.preventDefault();
      handleSave();
    }

    // Escape = Close modal
    if (event.key === 'Escape') {
      handleClose();
    }

    // Arrow keys = Navigation
    if (event.key === 'ArrowDown') {
      event.preventDefault();
      navigateDown();
    }
  };

  return (
    <div onKeyDown={handleKeyDown} tabIndex={0}>
      Press Ctrl+S to save
    </div>
  );
}

// Roving tabindex implementation
function Toolbar() {
  const [focusedIndex, setFocusedIndex] = useState(0);
  const buttonsRef = useRef<(HTMLButtonElement | null)[]>([]);

  const buttons = ['Cut', 'Copy', 'Paste', 'Undo', 'Redo'];

  const handleKeyDown = (event: React.KeyboardEvent, index: number) => {
    let newIndex = index;

    switch (event.key) {
      case 'ArrowRight':
        event.preventDefault();
        newIndex = Math.min(index + 1, buttons.length - 1);
        break;
      case 'ArrowLeft':
        event.preventDefault();
        newIndex = Math.max(index - 1, 0);
        break;
      case 'Home':
        event.preventDefault();
        newIndex = 0;
        break;
      case 'End':
        event.preventDefault();
        newIndex = buttons.length - 1;
        break;
      default:
        return;
    }

    setFocusedIndex(newIndex);
    buttonsRef.current[newIndex]?.focus();
  };

  return (
    <div role="toolbar" aria-label="Text formatting">
      {buttons.map((label, index) => (
        <button
          key={label}
          ref={(el) => (buttonsRef.current[index] = el)}
          tabIndex={index === focusedIndex ? 0 : -1}
          onKeyDown={(e) => handleKeyDown(e, index)}
          onClick={() => console.log(`${label} clicked`)}
        >
          {label}
        </button>
      ))}
    </div>
  );
}

// Focus trap for modal
function useFocusTrap(ref: React.RefObject<HTMLElement>) {
  useEffect(() => {
    const element = ref.current;
    if (!element) return;

    const focusableElements = element.querySelectorAll(
      'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'
    );
    const firstElement = focusableElements[0] as HTMLElement;
    const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;

    const handleTabKey = (e: KeyboardEvent) => {
      if (e.key !== 'Tab') return;

      if (e.shiftKey) {
        if (document.activeElement === firstElement) {
          lastElement.focus();
          e.preventDefault();
        }
      } else {
        if (document.activeElement === lastElement) {
          firstElement.focus();
          e.preventDefault();
        }
      }
    };

    element.addEventListener('keydown', handleTabKey);
    firstElement?.focus();

    return () => element.removeEventListener('keydown', handleTabKey);
  }, [ref]);
}

// Modal with focus management
function Modal({ isOpen, onClose, children }) {
  const modalRef = useRef<HTMLDivElement>(null);
  const previousFocusRef = useRef<HTMLElement | null>(null);

  useFocusTrap(modalRef);

  useEffect(() => {
    if (isOpen) {
      previousFocusRef.current = document.activeElement as HTMLElement;
    } else if (previousFocusRef.current) {
      previousFocusRef.current.focus();
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div
      ref={modalRef}
      role="dialog"
      aria-modal="true"
      onKeyDown={(e) => e.key === 'Escape' && onClose()}
    >
      {children}
      <button onClick={onClose}>Close</button>
    </div>
  );
}

// Custom dropdown with keyboard navigation
function Dropdown({ options }: { options: string[] }) {
  const [isOpen, setIsOpen] = useState(false);
  const [selectedIndex, setSelectedIndex] = useState(-1);
  const listRef = useRef<HTMLUListElement>(null);

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (!isOpen) {
      if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
        e.preventDefault();
        setIsOpen(true);
        setSelectedIndex(0);
      }
      return;
    }

    switch (e.key) {
      case 'ArrowDown':
        e.preventDefault();
        setSelectedIndex((prev) => Math.min(prev + 1, options.length - 1));
        break;
      case 'ArrowUp':
        e.preventDefault();
        setSelectedIndex((prev) => Math.max(prev - 1, 0));
        break;
      case 'Enter':
      case ' ':
        e.preventDefault();
        console.log('Selected:', options[selectedIndex]);
        setIsOpen(false);
        break;
      case 'Escape':
        e.preventDefault();
        setIsOpen(false);
        break;
    }
  };

  return (
    <div>
      <button
        onKeyDown={handleKeyDown}
        onClick={() => setIsOpen(!isOpen)}
        aria-haspopup="listbox"
        aria-expanded={isOpen}
      >
        Select option
      </button>
      {isOpen && (
        <ul ref={listRef} role="listbox">
          {options.map((option, index) => (
            <li
              key={option}
              role="option"
              aria-selected={index === selectedIndex}
              style={{
                background: index === selectedIndex ? '#e0e0e0' : 'transparent',
              }}
            >
              {option}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

6. Touch Events Mobile Gesture Handling

Event Trigger Description Use Case
touchstart Finger touches screen Fires when touch begins Gesture initiation
touchmove Finger moves on screen Fires repeatedly during touch movement Drag, swipe tracking
touchend Finger leaves screen Fires when touch ends Gesture completion
touches event.touches Array of all current touch points Multi-touch gestures
changedTouches event.changedTouches Touches that changed in this event Specific touch handling
Pointer Events onPointerDown/Move/Up Unified API for mouse, touch, pen Cross-device input

Example: Touch gestures with swipe and pinch detection

// Swipe gesture detection
function useSwipe(onSwipeLeft, onSwipeRight, threshold = 50) {
  const touchStart = useRef<{ x: number; y: number } | null>(null);

  const handleTouchStart = (e: React.TouchEvent) => {
    touchStart.current = {
      x: e.touches[0].clientX,
      y: e.touches[0].clientY,
    };
  };

  const handleTouchEnd = (e: React.TouchEvent) => {
    if (!touchStart.current) return;

    const touchEnd = {
      x: e.changedTouches[0].clientX,
      y: e.changedTouches[0].clientY,
    };

    const deltaX = touchEnd.x - touchStart.current.x;
    const deltaY = Math.abs(touchEnd.y - touchStart.current.y);

    // Horizontal swipe (deltaX > deltaY)
    if (Math.abs(deltaX) > threshold && Math.abs(deltaX) > deltaY) {
      if (deltaX > 0) {
        onSwipeRight?.();
      } else {
        onSwipeLeft?.();
      }
    }

    touchStart.current = null;
  };

  return { handleTouchStart, handleTouchEnd };
}

// Swipeable component
function SwipeableCard() {
  const { handleTouchStart, handleTouchEnd } = useSwipe(
    () => console.log('Swiped left'),
    () => console.log('Swiped right')
  );

  return (
    <div
      onTouchStart={handleTouchStart}
      onTouchEnd={handleTouchEnd}
      style={{
        width: '300px',
        height: '200px',
        background: '#f0f0f0',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
      }}
    >
      Swipe me left or right
    </div>
  );
}

// Draggable element with touch
function DraggableElement() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [isDragging, setIsDragging] = useState(false);
  const startPos = useRef({ x: 0, y: 0 });
  const currentPos = useRef({ x: 0, y: 0 });

  const handleTouchStart = (e: React.TouchEvent) => {
    setIsDragging(true);
    startPos.current = {
      x: e.touches[0].clientX - position.x,
      y: e.touches[0].clientY - position.y,
    };
  };

  const handleTouchMove = (e: React.TouchEvent) => {
    if (!isDragging) return;

    currentPos.current = {
      x: e.touches[0].clientX - startPos.current.x,
      y: e.touches[0].clientY - startPos.current.y,
    };

    setPosition(currentPos.current);
  };

  const handleTouchEnd = () => {
    setIsDragging(false);
  };

  return (
    <div
      onTouchStart={handleTouchStart}
      onTouchMove={handleTouchMove}
      onTouchEnd={handleTouchEnd}
      style={{
        position: 'absolute',
        left: `${position.x}px`,
        top: `${position.y}px`,
        width: '100px',
        height: '100px',
        background: '#4CAF50',
        cursor: isDragging ? 'grabbing' : 'grab',
        touchAction: 'none', // Prevent default touch behaviors
      }}
    >
      Drag me
    </div>
  );
}

// Pinch-to-zoom gesture
function usePinchZoom() {
  const [scale, setScale] = useState(1);
  const initialDistance = useRef<number | null>(null);
  const initialScale = useRef(1);

  const getDistance = (touch1: React.Touch, touch2: React.Touch) => {
    const dx = touch1.clientX - touch2.clientX;
    const dy = touch1.clientY - touch2.clientY;
    return Math.sqrt(dx * dx + dy * dy);
  };

  const handleTouchStart = (e: React.TouchEvent) => {
    if (e.touches.length === 2) {
      initialDistance.current = getDistance(e.touches[0], e.touches[1]);
      initialScale.current = scale;
    }
  };

  const handleTouchMove = (e: React.TouchEvent) => {
    if (e.touches.length === 2 && initialDistance.current) {
      const currentDistance = getDistance(e.touches[0], e.touches[1]);
      const newScale = (currentDistance / initialDistance.current) * initialScale.current;
      setScale(Math.min(Math.max(newScale, 0.5), 3)); // Limit scale 0.5x to 3x
    }
  };

  const handleTouchEnd = () => {
    initialDistance.current = null;
  };

  return { scale, handleTouchStart, handleTouchMove, handleTouchEnd };
}

// Zoomable image
function ZoomableImage({ src }: { src: string }) {
  const { scale, handleTouchStart, handleTouchMove, handleTouchEnd } = usePinchZoom();

  return (
    <div
      onTouchStart={handleTouchStart}
      onTouchMove={handleTouchMove}
      onTouchEnd={handleTouchEnd}
      style={{
        overflow: 'hidden',
        touchAction: 'none',
      }}
    >
      <img
        src={src}
        alt="Zoomable"
        style={{
          transform: `scale(${scale})`,
          transformOrigin: 'center center',
          transition: 'transform 0.1s',
        }}
      />
    </div>
  );
}

// Unified pointer events (mouse + touch + pen)
function UnifiedPointerHandler() {
  const [isPressed, setIsPressed] = useState(false);
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handlePointerDown = (e: React.PointerEvent) => {
    setIsPressed(true);
    (e.target as HTMLElement).setPointerCapture(e.pointerId);
  };

  const handlePointerMove = (e: React.PointerEvent) => {
    if (isPressed) {
      setPosition({ x: e.clientX, y: e.clientY });
    }
  };

  const handlePointerUp = (e: React.PointerEvent) => {
    setIsPressed(false);
    (e.target as HTMLElement).releasePointerCapture(e.pointerId);
  };

  return (
    <div
      onPointerDown={handlePointerDown}
      onPointerMove={handlePointerMove}
      onPointerUp={handlePointerUp}
      style={{
        width: '100%',
        height: '400px',
        background: '#f0f0f0',
        position: 'relative',
        touchAction: 'none',
      }}
    >
      <div
        style={{
          position: 'absolute',
          left: `${position.x}px`,
          top: `${position.y}px`,
          width: '20px',
          height: '20px',
          background: isPressed ? 'red' : 'blue',
          borderRadius: '50%',
          transform: 'translate(-50%, -50%)',
        }}
      />
    </div>
  );
}

Event Handling Best Practices

  • SyntheticEvent - React's cross-browser event wrapper, use preventDefault() for form submissions
  • Event Delegation - Single listener on parent for dynamic children, improves performance
  • Custom Events - Enable decoupled component communication via browser's event system
  • Debounce - For search, resize, input validation (wait for pause)
  • Throttle - For scroll, mouse move, animations (limit execution rate)
  • Keyboard Navigation - Roving tabindex for toolbars, arrow keys for lists, focus trap for modals
  • Touch Events - Use pointer events for unified mouse/touch/pen handling, prevent default behaviors with touchAction