React has its own event system that's similar to the DOM event system, but that works in parallel to it. That means that React event handlers are assigned differently than DOM event listeners.

React Event Handlers

You can handle events in React by passing an event handler function as a prop JSX element:

import { MouseEvent } from "react";

function MyComponent() {
  const handleButtonClick = (e: MouseEvent<HTMLButtonElement>) => {
    // Called when the button is clicked
  }
  
  return (
    <button onClick={handleButtonClick}>
      Click me!
    </button>
  )
}
Adding an event handler to React elements in TypeScript.

The event handler props are all camel-cased and accept a callback function. Some commonly used ones are onClick, onChange and onSubmit. The callback function will receive SyntheticEvent as the parameter.

DOM Event Handlers in React

It's sometimes necessary to use the DOM event system, for instance when integrating with 3rd party libraries or listening to window events.

You should use the useEffect hook to add event listeners in React. Here's how to listen to events on window in React:

function MyComponent() {
  useEffect(() => {
    const handleWindowResize = (e: UIEvent) => {
      // Called when browser window is resized
    };

    window.addEventListener("resize", handleWindowResize);
    return () => {
      window.removeEventListener("resize", handleWindowResize);
    };
  }, []);

  return <>...</>;
}
Adding an event handler to the window.
  1. Add a call to useEffect and provide [] as the dependency list to only register the event listener once when the component is mounted.
  2. Create a handler function inside of the useEffect hook's callback function.
  3. Bind the event handler to an event using addEventListener on the DOM element.
  4. Return a cleanup function for the useEffect hook that removes the assigned event handler.

Assigning DOM events to React elements

To assign the event listener to the React element you need to first get the reference to the underlying DOM element. This is commonly done using the useRef hook:

function MyComponent() {
  const buttonRef = useRef<HTMLButtonElement>(null);

  useEffect(() => {
  	// ๐Ÿ‘‡ Get the DOM element from the React element
    const element = buttonRef.current;
    
    if (!element) return;

    const handleButtonClick = (e: MouseEvent) => {
      // Called when button is clicked
    };

    element.addEventListener("click", handleButtonClick);
    return () => {
      element.removeEventListener("click", handleButtonClick);
    };
  }, []);

  return <button ref={buttonRef}>Click me!</button>;
}
Listening to DOM events on React elements using useRef to get their references.
  1. Call the useRef and assign its value to the ref prop on the React element.
  2. In the useEffect hook, get the reference to the DOM element from current property on the ref object. Check that it's not null.
  3. Add the DOM event listener like before using addEventListener.

Conclusion

Always prefer to use the React event system, because it fixes certain inconsistencies, and working with it is generally easier.

In the rare cases when you do need DOM event listeners, assign them in the useEffect hook. You should first have the reference to the DOM element which you can get using the useRef hook or simply by querying it directly, e.g. window, document.body or document.querySelector(...).