0

I'm facing an issue, which I don't even understand if it's because of ReactJS, or it's just JavaScript.
Basically, method document.elementsFromPoint() does't work if a ResizeObserver is observing an element of the DOM.

Here's a fiddle that reproduce the problem:

class App extends React.Component {
    divRef;
    resizeObserver;    
    
    constructor(props) {
        super(props);
        
        this.divRef = React.createRef();
        this.resizeObserver = new ResizeObserver(entries => {
            console.log("Div resized");
            this.forceUpdate();
        });
    }
    
    componentDidMount() {
        console.log("|| DIDMOUNT CONSOLE || ");
        console.log(document.elementsFromPoint(150,150));
        this.resizeObserver.observe(this.divRef.current);
    }
    
    componentDidUpdate() {
        console.log("|| DIDUPDATE CONSOLE || ");
        console.log(document.elementsFromPoint(150,150)); 
    }
    
    componentWillUnmount() {
        this.resizeObserver.unobserve(this.divRef.current);
    }
    
    render() {
        
        return (
            <div ref={this.divRef} className="home-div" />
        );
    }
}

ReactDOM.render(<App />, document.getElementById('root'));
@import url(https://fonts.googleapis.com/css?family=Montserrat);

body {
    font-family: 'Montserrat', sans-serif;
}

.home-div {
    background: blue;
    width: 500px;
    height: 200px;
    resize: both;
    overflow: auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id='root'></div>

As you can see, the log in componentDidMount(), which uses document.elementsFromPoint(), returns a bunch of elements.
Instead, the log in componentDidUpdate(), which again uses document.elementsFromPoint(), returns just the html element, not all the others (even if the point is exactly the same, (150, 150)).

Now, I thought that somehow React was re-rendering the whole DOM, thus, what's was before is no longer in the DOM, but there is no way componentDidUpdate() returns just the html element: componentDidUpdate() is called AFTER the render has finished, thus there no way there is only the html element.

Is this a bug? Or am I missing something, somewhere?

Jolly
  • 1,678
  • 2
  • 18
  • 37

1 Answers1

2

I guess it is a bug. Apparently, the observer callback is called at some weird state of the document when document.elementsFromPoint() returns only HTML element. You don't even need to call this.forceUpdate(), call elementsFromPoint directly inside the observer callback you'll have the same result. I have the same result each time when the div is resized.

I think componentDidUpdate logs the same since the forceUpdate -> render -> DOM updates -> componentDidUpdate call chain was synchronous and this weird DOM state persisted. Well, probably DOM state is only weird for elementsFromPoint call...

If you call forceUpdate asynchronously using setTimeout for example, elementsFromPoint will show all divs correctly.

GProst
  • 9,229
  • 3
  • 25
  • 47
  • Yeah, I did notice that even inside the callback `document.elementsFromPoint()` returned only `html` element, but to that reason I gave myself the answer that the observer is running in a service worker, so, maybe, it doesn't have the same "context" (in this case, the same document). Though, this explanation couldn't work for the React Component, since it doesn't run on the service worker. BTW, thanks for a sort of solution, but I can't use the `setTimeout()`, the behavior becames just kinda random :P Should I report this somewhere? – Jolly Mar 28 '19 at 08:10
  • I think it'd be nice if it was reported, you can check this [SO answer](https://stackoverflow.com/a/40714/6250385) to find where to create an issue. – GProst Mar 28 '19 at 12:50