JavaScript IntersectionObserver Incorrect Behaviour with DOM Layout changes

Expected Behaviour: IntersectionObserver must correctly report the observed component’s visibility in the viewport.
Current Behaviour: IntersectionObserver incorrectly reports component as invisible despite being in the viewport when another component is dragged and dropped to change the DOM layout.

enter image description here

My useOnScreen hook –

// intersection observer is buggy with golden layout
// move C component and keep all in view, but B component will be marked not on screen
function useOnScreen(label, ref) {
  const [isOnScreen, setIsOnScreen] = useState(false);
  const observerRef = useRef(null);

  useEffect(() => {
    observerRef.current = new IntersectionObserver(
      ([entry]) => {
        setIsOnScreen(entry.isIntersecting);
      },
      { root: document.body }
    );
  }, []);

  useEffect(() => {
    observerRef.current.observe(ref.current);

    return () => {
      observerRef.current.disconnect();
    };
  }, [ref]);

  return isOnScreen;
}

How I use this hook in my React component –

const Text = ({ label }) => {
  const ref = useRef(null);
  const onScreen = useOnScreen(label, ref);
  useEffect(() => {
    if (onScreen) {
      console.log("onscreen", label);
      return () => {
        console.log("not on screen", label);
      };
    }
  }, [onScreen, label]);

  return <h1 ref={ref}>{label}</h1>;
};

Demo Video of the bug: https://drive.google.com/file/d/1W54-D3BT7FNqZNS4EzBDYdJFqdHD-iCx/view?usp=sharing

Code Setup for the bug: https://codesandbox.io/s/golden-layout-react-intersection-observer-zhnq3c?file=/src/App.js:289-615


My Approach

  • I noticed that rootBounds.width and rootBounds.height are 0 in the IntersectionObserverEntry for component B when DOM layout is changed on drag/drop of component C.
  • Hence, it feels component B is not in view because the viewport is of 0 size
  • I even tried with passing options.root = document.body while creating the IntersectionObserver in its constructor but still same issue.
  • If I chose to ignore the callback when rootBounds is of 0 size, I will then miss legit cases where a component B is not in viewport but the hook will still keep it as visible.
  • The next solution I think is to hook into GoldenLayout events to track which tabs are active and which are not and pass that prop to my Components somehow.
  • Is there a better solution here?