funtkungus
funtkungus

Reputation: 244

How to get over Cannot read property 'removeEventListener' of null in react

I have some repeated hover states that runs a function to display some empty or filled icons (like you would see in some e-commerce websites with empty/filled carts). As practice, I wanted to create and put this into a custom hoverHooks component, w/ useRef and useEffect to run some add/remove eventListeners, likeso:

const ref = useRef(null)
function enter() {
  setHover(true)
}
function leave() {
  setHover(false)
}

useEffect(() => {
  ref.current.addEventListener('mouseenter',enter)
  ref.current.addEventListener('mouseleave', leave)
 return () => {
   ref.current.removeEventListener('mouseenter',enter)
   ref.current.removeEventListener('mouseleave',leave)
}
})

I did this so that the container holding my icons can just have the ref={ref} without me having to repeatedly write onMouseEnter / onMouseLeave. (I guess my refs are being repeated, but better three letters, and move my hover state to just one place. Cannot read property 'removeEventListener' of null, is what I get. I read the React 17 docs regarding this, under "potential issues." But their suggestion isn't working (capturing the mutable data by storing it into a variable).

useEffect(() => {
  const myRef = ref.current

  myRef.current.addEventListener('mouseenter',enter)
  myRef.current.addEventListener('mouseleave', leave)
 return () => {
   myRef.current.removeEventListener('mouseenter',enter)
   myRef.current.removeEventListener('mouseleave',leave)
}
})

Any and all advice would be greatly appreciated! Thank you

Upvotes: 5

Views: 16300

Answers (4)

Muhammed Ozdogan
Muhammed Ozdogan

Reputation: 5877

Instead of using ref.current in the cleanup function:

useEffect(() => {
  someRef.current.someSetupMethod();
  return () => {
    someRef.current.someCleanupMethod();
  };
});

Capture the ref inside the effect.

useEffect(() => {
  const instance = someRef.current;
  instance.someSetupMethod();
  return () => {
    instance.someCleanupMethod();
  };
});

Checkout the documentation for more details:

The problem is that someRef.current is mutable, so by the time the cleanup function runs, it may have been set to null. The solution is to capture any mutable values inside the effect:

Upvotes: 3

Prancer
Prancer

Reputation: 3546

I came across this recently in a test using jest and @testing-library/react. I was surprised none of automatic cleanup functions were handling this so I did some more digging and found an old git-issue which lead to this post in the react documentation (note, this is in reference to React v17.0 Release Candidate). To quote:

The problem is that someRef.current is mutable, so by the time the cleanup function runs, it may have been set to null. The solution is to capture any mutable values inside the effect:

useEffect(() => {
  const instance = someRef.current;
  instance.someSetupMethod();
  return () => {
    instance.someCleanupMethod();
  };
});

Again, this was in regards to v17.0 release candidate so I don't suspect this to be a problem in later versions. I am using React v17.0.2 which leads me to believe this problem stems from our code and/or how we are using react-hooks with third-party libs.

I figured I'd share this information anyways in case someone runs across this problem and is looking for a solution that avoids memory leaks from not cleaning stranded event listeners. Cheers

Upvotes: 2

Leo
Leo

Reputation: 17

There is solution from YouTube:

const refHandle = useRef();

<i ref={refHandle}> 

// This is to revise the original  
refHandle.current.removeEventListener('touchstart', onStartTouchBegin, false);

// To the conditional format,
(ref.current)?.removeEventListener('touchstart', onStartTouchBegin, false);

Upvotes: 2

cbdeveloper
cbdeveloper

Reputation: 31495

Is that what you are looking for?

/* HOVER HOOK */

const useHover = ({ ref, onMouseEnter, onMouseLeave }) => {

React.useEffect(() => {
  if (ref.current) {
    ref.current.addEventListener('mouseenter',onMouseEnter);
    ref.current.addEventListener('mouseleave',onMouseLeave);
  }
  return () => {
    ref.current.removeEventListener('mouseenter',onMouseEnter);
    ref.current.removeEventListener('mouseleave',onMouseLeave);
  };
},[ref,onMouseEnter,onMouseLeave]);
  
  return;

};

/* APP */

function App() {

  const ref = React.useRef(null);
  
  const onMouseEnter = () => console.log("ENTER");
  const onMouseLeave = () => console.log("LEAVE");
  
  useHover({ref,onMouseEnter,onMouseLeave});
  

  return(
    <div className="app" ref={ref}>
      Hover me
    </div>
  );
}

ReactDOM.render(<App/>, document.getElementById("root"));
.app {
  width: 100px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

Upvotes: 5

Related Questions