Event Handling and User Interactions

1. Event Handler Syntax and Event Objects

Syntax Pattern Description Use Case
Inline Arrow onClick={() => handler()} Arrow function in JSX Pass arguments, simple calls
Function Reference onClick={handler} Direct function reference Better performance, no args
Event Object (e) => handler(e) Access event properties Get target value, prevent default
With Parameters onClick={(e) => handler(id, e)} Pass custom args with event Item actions, list operations
Bind Method onClick={this.handler.bind(this, id)} Class component binding Legacy class components
Prevent Default e.preventDefault() Stop default browser action Forms, links, context menu
Stop Propagation e.stopPropagation() Stop event bubbling Nested clickable elements

Example: Event handler patterns

// Basic event handlers
const EventHandlers = () => {
  const [count, setCount] = useState(0);
  
  // Simple handler
  const handleClick = () => {
    setCount(c => c + 1);
  };
  
  // Handler with event object
  const handleChange = (e) => {
    console.log('Input value:', e.target.value);
  };
  
  // Handler with custom parameters
  const handleDelete = (id) => {
    console.log('Delete item:', id);
  };
  
  // Handler with both custom params and event
  const handleEdit = (id, e) => {
    e.stopPropagation(); // Stop bubbling
    console.log('Edit item:', id);
  };
  
  return (
    <div>
      {/* Function reference (preferred for simple cases) */}
      <button onClick={handleClick}>Count: {count}</button>
      
      {/* Inline arrow function (needed for params) */}
      <button onClick={() => handleDelete(123)}>Delete</button>
      
      {/* With event and params */}
      <button onClick={(e) => handleEdit(456, e)}>Edit</button>
      
      {/* Event object access */}
      <input onChange={handleChange} />
    </div>
  );
};

// Prevent default behavior
const FormWithLink = () => {
  const handleSubmit = (e) => {
    e.preventDefault(); // Don't reload page
    console.log('Form submitted');
  };
  
  const handleLinkClick = (e) => {
    e.preventDefault(); // Don't navigate
    console.log('Handle navigation in React');
  };
  
  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input type="text" />
        <button type="submit">Submit</button>
      </form>
      
      <a href="/page" onClick={handleLinkClick}>
        Custom navigation
      </a>
    </div>
  );
};

// Stop propagation for nested elements
const NestedClickables = () => {
  const handleParentClick = () => {
    console.log('Parent clicked');
  };
  
  const handleChildClick = (e) => {
    e.stopPropagation(); // Don't trigger parent
    console.log('Child clicked');
  };
  
  return (
    <div onClick={handleParentClick} style={{ padding: '2rem', border: '1px solid' }}>
      Parent
      <button onClick={handleChildClick}>
        Child (won't trigger parent)
      </button>
    </div>
  );
};

// Event object properties
const EventProperties = () => {
  const handleEvent = (e) => {
    console.log('Event type:', e.type);
    console.log('Target element:', e.target);
    console.log('Current target:', e.currentTarget);
    console.log('Key pressed:', e.key);
    console.log('Mouse position:', e.clientX, e.clientY);
    console.log('Modifier keys:', {
      ctrl: e.ctrlKey,
      shift: e.shiftKey,
      alt: e.altKey,
      meta: e.metaKey
    });
  };
  
  return (
    <div>
      <button onClick={handleEvent}>Click me</button>
      <input onKeyDown={handleEvent} />
    </div>
  );
};
Note: Use function references when possible for better performance. Use inline arrow functions only when you need to pass arguments or access closure variables.

2. Synthetic Events and Cross-browser Compatibility

Feature Description Benefits Important Notes
Synthetic Events React's wrapper around native events Cross-browser consistency Same API across all browsers
Event Pooling (Legacy) Events reused for performance Memory efficiency Removed in React 17+
nativeEvent e.nativeEvent access Access underlying browser event Use only when necessary
Event Delegation Events attached to root (React 16) Performance, memory efficiency Changed in React 17 (root container)
Passive Events Non-blocking scroll/touch events Better scroll performance Can't preventDefault() on passive

Example: Synthetic events and cross-browser handling

// Synthetic event properties
const SyntheticEventDemo = () => {
  const handleClick = (e) => {
    // Synthetic event properties (same across browsers)
    console.log('Synthetic event:', e);
    console.log('Type:', e.type);
    console.log('Target:', e.target);
    console.log('Timestamp:', e.timeStamp);
    
    // Access native browser event if needed
    console.log('Native event:', e.nativeEvent);
    
    // Common methods work consistently
    e.preventDefault();
    e.stopPropagation();
  };
  
  return <button onClick={handleClick}>Click</button>;
};

// No need to worry about event pooling in React 17+
const ModernEventHandling = () => {
  const handleClick = (e) => {
    // In React 17+, can access event properties asynchronously
    setTimeout(() => {
      console.log('Event type:', e.type); // Works!
      console.log('Target:', e.target);   // Works!
    }, 1000);
  };
  
  return <button onClick={handleClick}>Click</button>;
};

// Legacy React (<17) required persist() for async access
const LegacyAsyncEventAccess = () => {
  const handleClick = (e) => {
    e.persist(); // No longer needed in React 17+
    
    setTimeout(() => {
      console.log('Event type:', e.type);
    }, 1000);
  };
  
  return <button onClick={handleClick}>Click</button>;
};

// Native event access for special cases
const NativeEventAccess = () => {
  const handleWheel = (e) => {
    // Synthetic event
    console.log('Delta Y:', e.deltaY);
    
    // Native event for browser-specific features
    const nativeEvent = e.nativeEvent;
    console.log('Native wheel event:', nativeEvent);
  };
  
  return (
    <div
      onWheel={handleWheel}
      style={{ height: '200px', overflow: 'auto' }}
    >
      Scroll me
    </div>
  );
};

// Cross-browser event handling
const CrossBrowserEvents = () => {
  const handleInput = (e) => {
    // Works consistently across all browsers
    const value = e.target.value;
    console.log('Input value:', value);
  };
  
  const handlePaste = (e) => {
    // Clipboard access works consistently
    const pastedText = e.clipboardData.getData('text');
    console.log('Pasted:', pastedText);
  };
  
  const handleDrag = (e) => {
    // Drag events normalized
    console.log('Dragging:', e.dataTransfer);
  };
  
  return (
    <div>
      <input onChange={handleInput} onPaste={handlePaste} />
      <div draggable onDrag={handleDrag}>Drag me</div>
    </div>
  );
};

// Event delegation (automatic in React)
const EventDelegationExample = () => {
  // React automatically delegates events to root
  // No need to manually attach/remove listeners
  
  const handleClick = (id) => {
    console.log('Clicked item:', id);
  };
  
  return (
    <ul>
      {[1, 2, 3, 4, 5].map(id => (
        <li key={id} onClick={() => handleClick(id)}>
          Item {id}
        </li>
      ))}
    </ul>
  );
  // React attaches ONE listener at root, not 5 listeners
};
Note: React's synthetic events provide consistent behavior across browsers. Event pooling was removed in React 17+, so you can safely access event properties asynchronously without calling persist().

3. Event Handler Binding and Performance

Pattern Performance When to Use Example
Inline Arrow Creates new function each render Simple cases, rare renders onClick={() => handler()}
useCallback Memoized function reference Optimize child re-renders const cb = useCallback(() => {}, [])
Function Reference Best - stable reference No arguments needed onClick={handler}
Closure Factory Creates function per item List items with IDs const makeHandler = (id) => () => {}
Data Attributes Single handler, read from event Lists with simple actions data-id={id} then read in handler

Example: Event handler optimization

// ❌ Bad: Inline arrow in child with memo
const Child = memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Click</button>;
});

const BadParent = () => {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      {/* Child re-renders every time because new function */}
      <Child onClick={() => console.log('clicked')} />
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
    </div>
  );
};

// ✓ Good: useCallback for stable reference
const GoodParent = () => {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  
  return (
    <div>
      {/* Child won't re-render unnecessarily */}
      <Child onClick={handleClick} />
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
    </div>
  );
};

// ❌ Bad: Creating new function per list item
const BadList = () => {
  const [items, setItems] = useState([1, 2, 3, 4, 5]);
  
  const handleDelete = (id) => {
    setItems(items => items.filter(i => i !== id));
  };
  
  return (
    <ul>
      {items.map(item => (
        <li key={item}>
          {item}
          {/* New function created for each item */}
          <button onClick={() => handleDelete(item)}>Delete</button>
        </li>
      ))}
    </ul>
  );
};

// ✓ Better: Data attributes pattern
const BetterList = () => {
  const [items, setItems] = useState([1, 2, 3, 4, 5]);
  
  const handleDelete = (e) => {
    const id = parseInt(e.currentTarget.dataset.id);
    setItems(items => items.filter(i => i !== id));
  };
  
  return (
    <ul>
      {items.map(item => (
        <li key={item}>
          {item}
          {/* Single handler reference */}
          <button data-id={item} onClick={handleDelete}>Delete</button>
        </li>
      ))}
    </ul>
  );
};

// ✓ Good: Memoized list items
const ListItem = memo(({ item, onDelete }) => {
  return (
    <li>
      {item}
      <button onClick={onDelete}>Delete</button>
    </li>
  );
});

const OptimizedList = () => {
  const [items, setItems] = useState([1, 2, 3, 4, 5]);
  
  const handleDelete = useCallback((id) => {
    setItems(items => items.filter(i => i !== id));
  }, []);
  
  return (
    <ul>
      {items.map(item => (
        <ListItem
          key={item}
          item={item}
          onDelete={() => handleDelete(item)}
        />
      ))}
    </ul>
  );
};

// Performance optimization decision guide
const PerformanceGuide = () => {
  // Simple component, no memo on children → inline is fine
  const simple = () => (
    <button onClick={() => console.log('click')}>Click</button>
  );
  
  // Memo'ed children → use useCallback
  const [count, setCount] = useState(0);
  const optimized = useCallback(() => {
    console.log('click');
  }, []);
  
  // Large lists → use data attributes or memoized items
  // Rare updates → inline is fine
  // Frequent updates → optimize
};
Warning: Don't prematurely optimize! Inline arrow functions are fine for most cases. Only use useCallback when passing handlers to memoized children or in large lists with frequent updates.

4. Custom Event Handlers and Event Composition

Pattern Purpose Implementation Use Case
Event Composition Chain multiple handlers Call multiple functions in sequence Logging + action
Higher-Order Handlers Create handlers with common logic Function that returns handler Tracking, validation
Custom Events Component-specific events Custom event callbacks via props onSuccess, onError callbacks
Event Middlewares Transform or validate events Wrapper function around handler Auth checks, rate limiting
Debounced/Throttled Control event frequency useDebounce/useThrottle hooks Search input, scroll, resize

Example: Custom event handlers

// Event composition
const EventComposition = () => {
  const logEvent = (eventName) => {
    console.log(\`Event: \${eventName}\`);
  };
  
  const trackEvent = (eventName) => {
    analytics.track(eventName);
  };
  
  const handleClick = () => {
    console.log('Button clicked');
  };
  
  // Compose multiple handlers
  const composedHandler = (e) => {
    logEvent('button-click');
    trackEvent('button-click');
    handleClick();
  };
  
  return <button onClick={composedHandler}>Click</button>;
};

// Higher-order handler
const withTracking = (handler, eventName) => {
  return (e) => {
    analytics.track(eventName);
    handler(e);
  };
};

const withValidation = (handler, validate) => {
  return (e) => {
    if (validate(e)) {
      handler(e);
    }
  };
};

const TrackedButton = () => {
  const handleClick = () => {
    console.log('Clicked');
  };
  
  const trackedHandler = withTracking(handleClick, 'button-clicked');
  
  return <button onClick={trackedHandler}>Click</button>;
};

// Custom events via props
const FileUploader = ({ onUploadStart, onUploadSuccess, onUploadError }) => {
  const [uploading, setUploading] = useState(false);
  
  const handleUpload = async (file) => {
    setUploading(true);
    onUploadStart?.();
    
    try {
      const result = await uploadFile(file);
      onUploadSuccess?.(result);
    } catch (error) {
      onUploadError?.(error);
    } finally {
      setUploading(false);
    }
  };
  
  return (
    <input
      type="file"
      onChange={(e) => handleUpload(e.target.files[0])}
      disabled={uploading}
    />
  );
};

// Usage
const App = () => (
  <FileUploader
    onUploadStart={() => console.log('Starting...')}
    onUploadSuccess={(data) => console.log('Success:', data)}
    onUploadError={(err) => console.error('Error:', err)}
  />
);

// Debounced event handler
const useDebounce = (callback, delay) => {
  const timeoutRef = useRef(null);
  
  return useCallback((...args) => {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }
    
    timeoutRef.current = setTimeout(() => {
      callback(...args);
    }, delay);
  }, [callback, delay]);
};

const SearchInput = () => {
  const [query, setQuery] = useState('');
  
  const performSearch = (searchTerm) => {
    console.log('Searching for:', searchTerm);
    // API call here
  };
  
  const debouncedSearch = useDebounce(performSearch, 500);
  
  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value);
    debouncedSearch(value);
  };
  
  return <input value={query} onChange={handleChange} />;
};

// Throttled event handler
const useThrottle = (callback, delay) => {
  const lastRun = useRef(Date.now());
  
  return useCallback((...args) => {
    const now = Date.now();
    
    if (now - lastRun.current >= delay) {
      callback(...args);
      lastRun.current = now;
    }
  }, [callback, delay]);
};

const ScrollTracker = () => {
  const trackScroll = (scrollY) => {
    console.log('Scroll position:', scrollY);
  };
  
  const throttledTrack = useThrottle(() => {
    trackScroll(window.scrollY);
  }, 1000);
  
  useEffect(() => {
    window.addEventListener('scroll', throttledTrack);
    return () => window.removeEventListener('scroll', throttledTrack);
  }, [throttledTrack]);
  
  return <div>Scroll the page</div>;
};

// Event middleware pattern
const withAuth = (handler) => {
  return (e) => {
    const isAuthenticated = checkAuth();
    if (!isAuthenticated) {
      alert('Please log in');
      return;
    }
    handler(e);
  };
};

const ProtectedButton = () => {
  const handleAction = () => {
    console.log('Performing protected action');
  };
  
  return (
    <button onClick={withAuth(handleAction)}>
      Protected Action
    </button>
  );
};

5. Keyboard and Mouse Event Patterns

Event Type Event Name Common Properties Use Case
Keyboard onKeyDown, onKeyUp, onKeyPress key, code, keyCode, ctrlKey, shiftKey Shortcuts, form navigation, games
Mouse Click onClick, onDoubleClick, onContextMenu button, clientX, clientY Actions, menus, selection
Mouse Movement onMouseMove, onMouseEnter, onMouseLeave clientX, clientY, movementX, movementY Tooltips, drag, hover effects
Mouse Drag onMouseDown, onMouseMove, onMouseUp Track position changes Draggable elements, drawing
Wheel onWheel deltaX, deltaY, deltaZ Custom scrolling, zoom

Example: Keyboard and mouse events

// Keyboard shortcuts
const KeyboardShortcuts = () => {
  const handleKeyDown = (e) => {
    // Check for specific key
    if (e.key === 'Enter') {
      console.log('Enter pressed');
    }
    
    // Keyboard shortcuts with modifiers
    if (e.ctrlKey && e.key === 's') {
      e.preventDefault();
      console.log('Save (Ctrl+S)');
    }
    
    if (e.ctrlKey && e.shiftKey && e.key === 'P') {
      e.preventDefault();
      console.log('Open command palette (Ctrl+Shift+P)');
    }
    
    // Arrow keys
    switch (e.key) {
      case 'ArrowUp':
        console.log('Up');
        break;
      case 'ArrowDown':
        console.log('Down');
        break;
      case 'ArrowLeft':
        console.log('Left');
        break;
      case 'ArrowRight':
        console.log('Right');
        break;
    }
  };
  
  return (
    <div onKeyDown={handleKeyDown} tabIndex={0}>
      Press keys (Ctrl+S, Arrows, Enter)
    </div>
  );
};

// Key codes reference
const KeyboardReference = () => {
  const handleKeyDown = (e) => {
    console.log('Key:', e.key);           // 'a', 'Enter', 'ArrowUp'
    console.log('Code:', e.code);         // 'KeyA', 'Enter', 'ArrowUp'
    console.log('KeyCode:', e.keyCode);   // 65, 13, 38 (deprecated)
    console.log('Modifiers:', {
      ctrl: e.ctrlKey,
      shift: e.shiftKey,
      alt: e.altKey,
      meta: e.metaKey  // Cmd on Mac, Win on Windows
    });
  };
  
  return <input onKeyDown={handleKeyDown} />;
};

// Mouse events
const MouseEvents = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [isHovering, setIsHovering] = useState(false);
  
  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY });
  };
  
  const handleClick = (e) => {
    console.log('Click at:', e.clientX, e.clientY);
    console.log('Button:', e.button); // 0=left, 1=middle, 2=right
  };
  
  const handleDoubleClick = () => {
    console.log('Double clicked');
  };
  
  const handleContextMenu = (e) => {
    e.preventDefault(); // Prevent default context menu
    console.log('Right clicked');
  };
  
  return (
    <div
      onMouseMove={handleMouseMove}
      onClick={handleClick}
      onDoubleClick={handleDoubleClick}
      onContextMenu={handleContextMenu}
      onMouseEnter={() => setIsHovering(true)}
      onMouseLeave={() => setIsHovering(false)}
      style={{
        width: '100%',
        height: '300px',
        border: '1px solid',
        backgroundColor: isHovering ? '#f0f0f0' : 'white'
      }}
    >
      Mouse position: {position.x}, {position.y}
      {isHovering && ' (Hovering)'}
    </div>
  );
};

// Draggable element
const DraggableBox = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [isDragging, setIsDragging] = useState(false);
  const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
  
  const handleMouseDown = (e) => {
    setIsDragging(true);
    setDragStart({
      x: e.clientX - position.x,
      y: e.clientY - position.y
    });
  };
  
  const handleMouseMove = (e) => {
    if (isDragging) {
      setPosition({
        x: e.clientX - dragStart.x,
        y: e.clientY - dragStart.y
      });
    }
  };
  
  const handleMouseUp = () => {
    setIsDragging(false);
  };
  
  useEffect(() => {
    if (isDragging) {
      window.addEventListener('mousemove', handleMouseMove);
      window.addEventListener('mouseup', handleMouseUp);
      
      return () => {
        window.removeEventListener('mousemove', handleMouseMove);
        window.removeEventListener('mouseup', handleMouseUp);
      };
    }
  }, [isDragging, dragStart]);
  
  return (
    <div
      onMouseDown={handleMouseDown}
      style={{
        position: 'absolute',
        left: position.x,
        top: position.y,
        width: '100px',
        height: '100px',
        backgroundColor: 'blue',
        cursor: isDragging ? 'grabbing' : 'grab'
      }}
    >
      Drag me
    </div>
  );
};

// Hover tooltip
const HoverTooltip = ({ children, tooltip }) => {
  const [show, setShow] = useState(false);
  const [position, setPosition] = useState({ x: 0, y: 0 });
  
  const handleMouseEnter = (e) => {
    setShow(true);
    setPosition({ x: e.clientX, y: e.clientY });
  };
  
  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY });
  };
  
  return (
    <>
      <span
        onMouseEnter={handleMouseEnter}
        onMouseMove={handleMouseMove}
        onMouseLeave={() => setShow(false)}
      >
        {children}
      </span>
      {show && (
        <div
          style={{
            position: 'fixed',
            left: position.x + 10,
            top: position.y + 10,
            background: 'black',
            color: 'white',
            padding: '5px',
            borderRadius: '3px'
          }}
        >
          {tooltip}
        </div>
      )}
    </>
  );
};

6. Touch Events and Mobile Interaction Handling

Event Description Properties Use Case
onTouchStart Touch begins touches, targetTouches, changedTouches Detect touch, start gesture
onTouchMove Touch moves Touch coordinates, track movement Swipe, drag, pan
onTouchEnd Touch ends Remaining touches Complete gesture, trigger action
onTouchCancel Touch interrupted System interruption Reset gesture state
Multi-touch Multiple fingers touches array Pinch zoom, rotate
Gestures Swipe, pinch, rotate Calculate from touch positions Mobile interactions

Example: Touch events and mobile gestures

// Basic touch events
const TouchDemo = () => {
  const [touchInfo, setTouchInfo] = useState('');
  
  const handleTouchStart = (e) => {
    const touch = e.touches[0];
    setTouchInfo(\`Touch started at \${touch.clientX}, \${touch.clientY}\`);
  };
  
  const handleTouchMove = (e) => {
    const touch = e.touches[0];
    setTouchInfo(\`Moving at \${touch.clientX}, \${touch.clientY}\`);
  };
  
  const handleTouchEnd = () => {
    setTouchInfo('Touch ended');
  };
  
  return (
    <div
      onTouchStart={handleTouchStart}
      onTouchMove={handleTouchMove}
      onTouchEnd={handleTouchEnd}
      style={{
        width: '100%',
        height: '200px',
        border: '1px solid',
        touchAction: 'none' // Prevent default touch behavior
      }}
    >
      {touchInfo || 'Touch here'}
    </div>
  );
};

// Swipe detection
const SwipeDetector = ({ onSwipeLeft, onSwipeRight, onSwipeUp, onSwipeDown }) => {
  const [touchStart, setTouchStart] = useState(null);
  
  const handleTouchStart = (e) => {
    const touch = e.touches[0];
    setTouchStart({ x: touch.clientX, y: touch.clientY });
  };
  
  const handleTouchEnd = (e) => {
    if (!touchStart) return;
    
    const touch = e.changedTouches[0];
    const deltaX = touch.clientX - touchStart.x;
    const deltaY = touch.clientY - touchStart.y;
    
    const threshold = 50;
    
    // Determine swipe direction
    if (Math.abs(deltaX) > Math.abs(deltaY)) {
      // Horizontal swipe
      if (deltaX > threshold) {
        onSwipeRight?.();
      } else if (deltaX < -threshold) {
        onSwipeLeft?.();
      }
    } else {
      // Vertical swipe
      if (deltaY > threshold) {
        onSwipeDown?.();
      } else if (deltaY < -threshold) {
        onSwipeUp?.();
      }
    }
    
    setTouchStart(null);
  };
  
  return (
    <div
      onTouchStart={handleTouchStart}
      onTouchEnd={handleTouchEnd}
      style={{
        width: '100%',
        height: '300px',
        border: '1px solid',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center'
      }}
    >
      Swipe in any direction
    </div>
  );
};

// Usage
const SwipeDemo = () => (
  <SwipeDetector
    onSwipeLeft={() => console.log('Swiped left')}
    onSwipeRight={() => console.log('Swiped right')}
    onSwipeUp={() => console.log('Swiped up')}
    onSwipeDown={() => console.log('Swiped down')}
  />
);

// Pinch zoom
const PinchZoom = () => {
  const [scale, setScale] = useState(1);
  const [initialDistance, setInitialDistance] = useState(null);
  
  const getDistance = (touch1, touch2) => {
    const dx = touch1.clientX - touch2.clientX;
    const dy = touch1.clientY - touch2.clientY;
    return Math.sqrt(dx * dx + dy * dy);
  };
  
  const handleTouchStart = (e) => {
    if (e.touches.length === 2) {
      const distance = getDistance(e.touches[0], e.touches[1]);
      setInitialDistance(distance);
    }
  };
  
  const handleTouchMove = (e) => {
    if (e.touches.length === 2 && initialDistance) {
      e.preventDefault();
      
      const distance = getDistance(e.touches[0], e.touches[1]);
      const newScale = (distance / initialDistance) * scale;
      
      // Clamp scale between 0.5 and 3
      setScale(Math.min(Math.max(newScale, 0.5), 3));
    }
  };
  
  const handleTouchEnd = () => {
    setInitialDistance(null);
  };
  
  return (
    <div
      onTouchStart={handleTouchStart}
      onTouchMove={handleTouchMove}
      onTouchEnd={handleTouchEnd}
      style={{
        width: '300px',
        height: '300px',
        border: '1px solid',
        overflow: 'hidden',
        touchAction: 'none'
      }}
    >
      <div
        style={{
          transform: \`scale(\${scale})\`,
          transformOrigin: 'center',
          transition: initialDistance ? 'none' : 'transform 0.3s'
        }}
      >
        Pinch to zoom (scale: {scale.toFixed(2)})
      </div>
    </div>
  );
};

// Draggable with touch support
const TouchDraggable = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [isDragging, setIsDragging] = useState(false);
  const [startPos, setStartPos] = useState({ x: 0, y: 0 });
  
  const handleStart = (clientX, clientY) => {
    setIsDragging(true);
    setStartPos({
      x: clientX - position.x,
      y: clientY - position.y
    });
  };
  
  const handleMove = (clientX, clientY) => {
    if (isDragging) {
      setPosition({
        x: clientX - startPos.x,
        y: clientY - startPos.y
      });
    }
  };
  
  // Touch handlers
  const handleTouchStart = (e) => {
    const touch = e.touches[0];
    handleStart(touch.clientX, touch.clientY);
  };
  
  const handleTouchMove = (e) => {
    e.preventDefault();
    const touch = e.touches[0];
    handleMove(touch.clientX, touch.clientY);
  };
  
  // Mouse handlers (for desktop)
  const handleMouseDown = (e) => {
    handleStart(e.clientX, e.clientY);
  };
  
  const handleMouseMove = (e) => {
    handleMove(e.clientX, e.clientY);
  };
  
  const handleEnd = () => {
    setIsDragging(false);
  };
  
  useEffect(() => {
    if (isDragging) {
      window.addEventListener('mousemove', handleMouseMove);
      window.addEventListener('mouseup', handleEnd);
      
      return () => {
        window.removeEventListener('mousemove', handleMouseMove);
        window.removeEventListener('mouseup', handleEnd);
      };
    }
  }, [isDragging]);
  
  return (
    <div
      onMouseDown={handleMouseDown}
      onTouchStart={handleTouchStart}
      onTouchMove={handleTouchMove}
      onTouchEnd={handleEnd}
      style={{
        position: 'absolute',
        left: position.x,
        top: position.y,
        width: '100px',
        height: '100px',
        backgroundColor: 'green',
        touchAction: 'none'
      }}
    >
      Drag me
    </div>
  );
};
Warning: Always set touchAction: 'none' CSS property when handling touch events to prevent default browser behaviors. Use e.preventDefault() carefully as it can interfere with scrolling.

Event Handling Best Practices

  • Function references: Use direct references when possible for better performance
  • useCallback: Memoize handlers passed to memo'ed children or large lists
  • Event delegation: React handles automatically - don't manually manage listeners
  • preventDefault/stopPropagation: Use judiciously to control event flow
  • Touch events: Support both mouse and touch for best mobile/desktop experience
  • Debounce/throttle: Control frequency for expensive operations (search, scroll)