Correct way to use addEventListener in React components
While writing source code in React, if we are beginners, we do not think about correct way of using addEventListener()
functions - even, we do not think why we should use it.
You may ask: Why I should use addEventListener()
if React provides API with events in props?
Let me explain: Some events that are not available by default in React props, like e.g. window
key events.
In this article I am going to expalin what is the common mistakes and how to deal with them.
Code with bugs
I will explain the problem on component that listens for Ctrl
+a
keys pressed. The component that uses addEventListener()
is unmounter and mounted after clicks to the button.
const MyComponent = () => {
useEffect(() => {
window.addEventListener('keyup', (e) => {
// source code here ...
});
// <-------- bug is here
},[]);
return (
<div>{/* components here ... */}</div>
);
};
Always when MyComponent
is re-rendered keyup
event listener is added to window
object. After long time of the application working we have hundreds or even more listeners that calls callbacks.
That approach may lead to serious bugs, and performance issues after long application usage.
Fixed code
To solve the problem it is necessary just to call function that cleans up component. That function is the function returned from useEffect()
function.
So:
useEffect(() => {
const callback = (e) => {
setVisible(e.ctrlKey && e.key === 'a');
};
window.addEventListener('keyup', callback);
return () => window.removeEventListener('keyup', callback); // <-------- fix is here
},[]);
The correct source code should be:
const MyComponent = () => {
useEffect(() => {
const callback = (e) => {
// source code here ...
};
window.addEventListener('keyup', callback);
return () => window.removeEventListener('keyup', callback); // <-------- fix is here
},[]);
return (
<div>{/* components here ... */}</div>
);
};
Hint: check this article to see runable example.
Reusable event hook
It is the best to use some hook that releases events for us by self.
The hook can may be created in the following way:
const useEvent = (object, event, callback) => {
useEffect(() =>{
object.addEventListener(event, callback);
return () => object.removeEventListener(event, callback);
}, []);
};
The correct source code with event hook will be:
const useEvent = (object, event, callback) => {
useEffect(() =>{
object.addEventListener(event, callback);
return () => object.removeEventListener(event, callback);
}, []);
};
const MyComponent = () => {
useEvent(window, 'keyup', (e) => {
// source code here ...
});
return (
<div>{/* components here ... */}</div>
);
};
Conclusions
- React do not provide all pure JavaScript features.
- Whe we call that JS API we should take care of cleaning up after components were unmounted. The good exampel is
addEventListener()
function, what was described in the article. - To simplyfy the problem with removing events in components we can use custom
useEvent()
hook.