Problem
An application requires the inner size of the window. React patterns suggests registering an event listener within a one-time effect hook. The call to window.addEventListener appears to occur only once, but event listeners pile up and negatively affect performance.
Code
Here's the pared down source code that reproduces this issue
import React, {useState, useEffect} from 'react';
const getWindowRect = () => {
return new DOMRect(0, 0, window.innerWidth, window.innerHeight);
}
// custom hook to track the window dimensions
const useWindowRect = () => {
// use state for the aspect ratio
let [rect, setRect] = useState(getWindowRect);
// useEffect w/o deps should only be called once
useEffect(() => {
const resizeHandler = () => { setRect(getWindowRect()); };
window.addEventListener('resize', resizeHandler);
console.log('added resize listener');
// return the cleanup function
return () => {
window.removeEventListener('resize', resizeHandler);
console.log('removed resize listener');
}
}, []);
// return the up-to-date window rect
return rect;
}
const App = () => {
const window_rect = useWindowRect();
return <div>
{window_rect.width/window_rect.height}
</div>
};
export default App;
Testing
relevant console output reads:
added resize listener
this is the expected result where the listener is added only once, no matter how much the app is re-rendered
reference, window not resized max listeners: 56

resizing performance, hundreds of listeners accumulate max listeners: 900+

resizing performance w/ window.addEventListener commented out max listeners: 49

Environment
- React 16.13.1
- TypeScript 4.0.3
- WebPack 4.44.2
- Babel Loader 8.1.0
- Chrome 86.0.4240.111 (Official Build) (x86_64)
Demo
Assuming is would be difficult to run performance metrics on a JSFiddle or CodePen I've provided a full demo at this repo: oclyke-exploration/resize-handler-performance You can easily run the demo as long as you have node and yarn installed.
General Discussion
- this approach has worked before w/o these symptoms, however the environment was slightly different and did not include TypeScript (could this be caused by the cross-compilation?)
- i've briefly looked into whether the function reference that is provided to
window.removeEventListeneris the same as that provided towindow.addEventListener- though this should not even come into play when the effect only occurs once - there are many possible ways to work around this issue - this question is intended to ask why this method, which is expected to work, does not
- reproduced this issue on a fresh
create-react-appproject using react-scripts 4.0.0
Ask
Does anyone have an explanation for this issue? I'm stumped! (related: can others reproduce this issue?)