How can one prevent excess JS event handlers in React?

Viewed 153

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 reference performance

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

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

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.removeEventListener is the same as that provided to window.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-app project using react-scripts 4.0.0

Ask

Does anyone have an explanation for this issue? I'm stumped! (related: can others reproduce this issue?)

1 Answers

As pointed out by Patrick Roberts and Aleksey L. in comments on the question the issue is not actually an issue because:

Related