A better way to create event listeners in React
In JavaScript, we can write code that listens and reacts to various events using DOM event listeners. This can be easily accomplished by using the addEventListener
method, which is available on any HTML element. However, in React, calling addEventListener
directly on an element can cause some performance issues.
In this short article, we will learn a better and more optimized way of creating event listeners in React.
TL;DR: Use
useRef
to reference the element and add the event listener to the ref instead of adding the event listener directly to the element.
Adding Eventlisteners using Ref in React.
Instead of adding event listeners directly to the document object or HTML element as we do in vanilla JavaScript, we can create a reference to it using React’s useRef hook (or createRef method, which is used in class-based components). Then, we can add the event listener to the ref (reference) instead of directly to the element.
Let's take a look at how this is done in the example below:
Here is an example of the JavaScript way of adding an event listener to the document
object:
document.addEventListener("keydown", (e) => {
// Code
});
However, in React, it is better to create a reference to the document
object or HTML element using the useRef
hook.
Here is an example of creating a reference to the document
object:
import { useRef } from "react";
const documentRef = useRef(document); // document here is window.document
documentRef.current.addEventListener("keydown", () => {
// Code
});
Alternatively, if we want to listen to an element and not the document
object, we can create a reference to an HTML element and add the event listener to that reference:
import { useRef } from "react";
const App = () => {
const divRef = useRef(); // Ref for HTML Element
divRef.current.addEventListener("keydown", () => {
// Code
});
return (
<div ref={divRef}>
<p>Hello World!</p>
</div>
);
}
Why is this important?
It’s important for performance. By using useRef
or createRef
and adding event listeners to the ref instead of directly to the document or HTML element, we avoid unnecessary re-renders and improve performance. This is because adding event listeners directly to the document or HTML element can cause React to re-render the component, even when the changes made by the event listener are not relevant to the component itself. By using a ref, we can ensure that the component only re-renders when its own state or props change, and not because of changes made by event listeners.
Other Performance tips.
If the event listener is in a
useEffect
(as would be the case 99% of the time), always unsubscribe from the event when the page/component gets unmounted. Adding event listeners without properly removing them can lead to memory leaks and degraded performance over time, especially in complex applications.
Example below:
import { useEffect, useRef } from 'react';
const documentRef = useRef(document);
const handleKeyDown = (e) => {
if (e.key === "Escape") {
// Run for your dear life
}
};
useEffect(() => {
documentRef.current.addEventListener("keydown", handleKeyDown);
return () => {
documentRef.current.removeEventListener("keydown", handleKeyDown);
};
}, []);
Memoize your event handler functions with
useCallback
to also avoid unnecessary re-renders because when usinguseCallback
the function reference doesn’t change unnecessarily.Let’s refactor the above example to use
useCallback
import { useCallback, useEffect, useRef } from 'react';
const documentRef = useRef(document);
const handleKeyDown = useCallback((e) => {
if (e.key === "Escape") {
// Run for your dear life
}
}, []);
useEffect(() => {
documentRef.current.addEventListener("keydown", handleKeyDown);
return () => {
documentRef.current.removeEventListener("keydown", handleKeyDown);
};
}, []);
Conclusion
As software developers, one of our duties is to write efficient and highly performant code. In this article, we learned how to improve performance in React by using useRef
or createRef
when adding event listeners to elements or the document
object. We also learned how to memoize event handler functions with useCallback
, and unsubscribe from events when the component unmounts, to further optimize our code for better performance. These practices can help prevent unnecessary re-renders and improve the overall efficiency of our React applications.
It’s important to note that in this article, I used function-based components; hence, all the code snippets utilize the useRef
hook. To use this approach in class-based components, you will have to create refs with createRef
method.
Resources
useRef, useCallback hooks.
React createRef method.
Published Sep 6th, 2024
#react
#performance
#eventlistener
#javascript
#best-practices