# How to Use React addEventListener for Custom Events: A Complete Guide

> Learn to efficiently use React addEventListener for custom events with useEffect. Attach and remove listeners in React components to manage interactions effectively. Complete guide.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: how-to-guide
- Published: 2026-02-20

---

**Use the native `addEventListener` API inside a `useEffect` hook (or `componentDidMount` in class components) to attach custom event listeners to DOM nodes, and always return a cleanup function to remove the listener on unmount.**

React's synthetic event system handles most user interactions through delegated listeners attached to the root container, but when you need to respond to custom DOM events—such as third-party library events, element-specific `resize` events, or manually dispatched events—you must implement `react addeventlistener` manually. This guide demonstrates how to efficiently manage native event listeners in React components while preventing memory leaks and performance issues, based on the implementation details found in the `facebook/react` repository.

## Why React addEventListener is Necessary for Custom Events

React's internal event system uses a delegation model where a single listener at the root handles all standard interactions. According to the source code in [`packages/react-dom/src/events/plugins/SimpleEventPlugin.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/events/plugins/SimpleEventPlugin.js), React maintains a registry of known DOM events (like `click`, `change`, `input`) that it can normalize into synthetic events.

Custom events—those not listed in `SimpleEventPlugin`—bypass this system entirely. The [`packages/react-dom-bindings/src/events/ReactDOMEventListener.js`](https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/events/ReactDOMEventListener.js) file shows how React attaches its root-level delegated listeners, but it cannot intercept arbitrary custom events dispatched via `new CustomEvent()`. Therefore, to listen for these events, you must access the underlying DOM node via refs and call `addEventListener` directly.

## Best Practices for React addEventListener

### Preventing Memory Leaks with Cleanup Functions

The most critical aspect of using native event listeners in React is ensuring they are removed when the component unmounts. In functional components, the cleanup function returned from `useEffect` serves this purpose. In class components, use `componentWillUnmount`.

### Optimizing Performance with Stable References

To prevent unnecessary re-attachments of event listeners, wrap your handler in `useCallback` to maintain a stable function reference. This ensures the `useEffect` dependency array remains stable, reducing the frequency of listener teardown and re-creation.

### Using Passive Listeners for Scroll Events

For scroll-related events, pass `{ passive: true }` as the third argument to `addEventListener`. This tells the browser not to block scrolling while waiting for JavaScript execution, significantly improving performance on mobile devices.

## Implementation Examples

### Functional Components with useEffect

The most common pattern for `react addeventlistener` in modern React uses the `useEffect` hook combined with `useRef` to access the DOM node:

```typescript
import { useEffect, useRef, useCallback } from 'react';

function CustomEventComponent() {
  const elementRef = useRef<HTMLDivElement>(null);

  const handleCustomEvent = useCallback((event: Event) => {
    const customEvent = event as CustomEvent;
    console.log('Received custom event:', customEvent.detail);
  }, []);

  useEffect(() => {
    const node = elementRef.current;
    if (!node) return;

    node.addEventListener('my-custom-event', handleCustomEvent);

    return () => {
      node.removeEventListener('my-custom-event', handleCustomEvent);
    };
  }, [handleCustomEvent]);

  return (
    <div ref={elementRef}>
      Component listening for custom events
    </div>
  );
}

```

This approach ensures the listener is attached only once after mount and properly cleaned up before unmount, preventing memory leaks.

### Class Components with componentDidMount

For class components, attach listeners in `componentDidMount` and remove them in `componentWillUnmount`:

```typescript
import React from 'react';

class LegacyEventComponent extends React.Component {
  constructor(props) {
    super(props);
    this.containerRef = React.createRef();
    this.handleEvent = this.handleEvent.bind(this);
  }

  componentDidMount() {
    const node = this.containerRef.current;
    if (node) {
      node.addEventListener('custom-action', this.handleEvent);
    }
  }

  componentWillUnmount() {
    const node = this.containerRef.current;
    if (node) {
      node.removeEventListener('custom-action', this.handleEvent);
    }
  }

  handleEvent(event) {
    console.log('Class component received:', event.detail);
  }

  render() {
    return (
      <div ref={this.containerRef}>
        Class-based custom event listener
      </div>
    );
  }
}

```

### Dispatching Custom Events

To trigger these listeners from anywhere in your application or from third-party libraries:

```javascript
const targetElement = document.getElementById('my-element');
if (targetElement) {
  targetElement.dispatchEvent(
    new CustomEvent('my-custom-event', {
      detail: { timestamp: Date.now(), data: 'payload' },
      bubbles: true,
      cancelable: true
    })
  );
}

```

## Understanding React's Event System Internals

React's synthetic event system is implemented across several key files in the `facebook/react` repository:

- **[`packages/react-dom-bindings/src/events/ReactDOMEventListener.js`](https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/events/ReactDOMEventListener.js)**: This file contains the logic for attaching React's root-level event listeners. It demonstrates how React uses event delegation to minimize the number of native listeners attached to the DOM.

- **[`packages/react-dom/src/events/plugins/SimpleEventPlugin.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/events/plugins/SimpleEventPlugin.js)**: This plugin registers the standard DOM events that React knows how to handle (click, change, input, etc.). Custom events are absent from this registry, which explains why they require manual `addEventListener` calls.

- **[`packages/react-dom/src/events/DOMPluginEventSystem.js`](https://github.com/facebook/react/blob/main/packages/react-dom/src/events/DOMPluginEventSystem.js)**: This file implements the mapping between native DOM events and React's synthetic event system. Understanding this architecture helps clarify why custom events bypass React's normalization and bubbling management.

These implementation details confirm that while React's synthetic event system is highly optimized for standard interactions, it intentionally leaves custom event handling to native browser APIs.

## Summary

- **Use `react addeventlistener`** only for custom or non-standard DOM events that React's synthetic system cannot handle, such as third-party library events or manually dispatched `CustomEvent` instances.
- **Always implement cleanup** by returning a removal function from `useEffect` or using `componentWillUnmount` in class components to prevent memory leaks.
- **Stabilize handler references** with `useCallback` to avoid unnecessary listener re-attachment cycles.
- **Use passive listeners** (`{ passive: true }`) for scroll events to improve performance on mobile devices.
- **Access DOM nodes via refs** (`useRef` or `createRef`) rather than querying the DOM directly to maintain React's component encapsulation.

## Frequently Asked Questions

### When should I use react addeventlistener instead of synthetic events?

Use `react addeventlistener` when handling events that React's synthetic event system does not recognize, such as custom events dispatched via `new CustomEvent()`, events from third-party libraries that emit native DOM events, or element-specific events like `resize` on a div (which React does not expose via props). For standard interactions like clicks, form changes, or keyboard events, always prefer React's `onClick`, `onChange`, and other synthetic event props.

### How do I remove event listeners in react to prevent memory leaks?

In functional components, return a cleanup function from the `useEffect` hook that calls `removeEventListener` with the same arguments used in `addEventListener`. In class components, implement `componentWillUnmount` and remove the listener there. Failing to remove listeners causes the component's handler function (which may reference component state or props) to remain in memory, preventing garbage collection of the entire component instance.

### Can I use react addeventlistener with hooks?

Yes, hooks provide the most ergonomic way to use `react addeventlistener`. Combine `useRef` to access the DOM node, `useCallback` to create a stable handler reference, and `useEffect` to attach and clean up the listener. This pattern ensures the listener is only attached after the DOM node is available and properly removed when the component unmounts or when dependencies change.

### What is the difference between React's onClick and addEventListener?

React's `onClick` prop uses the synthetic event system, which normalizes event behavior across browsers, implements event pooling (in React 16 and earlier), and delegates events to the root container for better performance. In contrast, `addEventListener` attaches a native listener directly to the DOM node, bypassing React's normalization and delegation. Use `onClick` for standard React interactions; use `addEventListener` only when integrating with non-React code or handling custom events that React does not support.