Should I call ResizeObserver.unobserve for removed elements?

8.8k views Asked by At

When some observed element gets removed from DOM, should I call .unobserve for that element to prevent memory leaks, or will it be "unobserved automatically"?

const ro = new ResizeObserver((entries) => { console.log(entries); });
const el = document.getElementById('foo');
ro.observe(el);
// ... some time later
el.remove();
ro.unobserve(el); // <-- is this needed, or does it happen automatically behind the scenes?

Why I'm asking: I'm implementing a React component that observes many children and properly cleaning up the observer for unmounted components would involve non-trivial code, which I want to avoid if it is actually unneeded.

2

There are 2 answers

0
mcmimik On BEST ANSWER

According to the issue in the csswg-drafts repository, today Chrome automatically unobserves the element if you remove it from DOM and delete any references from JS.

let el = document.querySelector("#id");
let ro = new ResizeObserver();
ro.observe(el);
setTimeout(_ => {
  el.remove();
  el = null;
}

Current Chrome code implicitly unobserves element if there are no JS references to it. This is not captured by the specification.

But the issue is still open...

0
Duncan Brain On

Yes you should use unobserve() or diconnect(). For my own project I was getting memory leaks in my application when I did not use it.

I also found the use of .unobserve() confusing. But if a teardown is provided.. generally speaking it is probably good practice to use it. Definitely easier to add while you are developing the piece of code versus updating in the future after you have forgotten why you did something.

I am using React too but I am using the useEffect hook to clean it up.

I had to do some trial and error to find out what worked, and also originally missed the .disconnect() call for ResizeObserver.

So currently what IS working for me is:

const [height, setHeight] = useState(null);
useEffect(() => {
  const heightObserver = new ResizeObserver((entries) => {
    setHeight(entries[0].contentRect['height']);
  });
  heightObserver.observe(el);

  return () => {
    if(el){
      heightObserver.unobserve(el);
    } else {
      heightObserver.disconnect();
    }
  }
},[]);

What did NOT work for me in my trial and error was the following.

This would lead to an 'el does not exist' error:

...
  return () => {
    heightObserver.unobserve(el);
  }
...

I can test for that but it would lead back to a memory leak:

...
  return () => {
    if(el){
      heightObserver.unobserve(el);
    }
  }
...

I then discovered .disconnect() and while there was no browser issues unfortunately the desired behaviour stopped functioning with just:

...
  return () => {
    heightObserver.disconnect();
  }
...

So that was my process, hopefully it helps someone. I still feel like there may be some better way to do what I am doing, but from what I can tell this works.