Here is a basic example of the issue. A component contains two nested div
s. We can keep track of the “depth” of the mouse position (0
if outside both div
s, 1
if inside the outer div
but outside the inner div
, 2
if inside the inner div
) by using useState
to store the current depth and creating two useCallback
-ed event handlers, one which increments the depth value and one which decrements it (then attaching these event handlers appropriately).
This mostly works fine, but if we move the mouse as fast as we can from inside the inner div to the outside, sometimes React fails to rerender the component between the firing of the events, so both copies of the decrement event handler run within the same render, and we have a race condition — the component is left thinking the depth value is 1
. The snippet below showcases exactly this (move your mouse slowly into the blue then very fast out back to the white).
What’s the best way to modify the situation to prevent this? My current best idea is to add a queue (of increments/decrements) to the component and some kind of useEffect(..., [ queue ])
to guarantee processing the queue one operation at a time. It would be nice if anyone can offer something simpler/more ergonomic (in the real application, operations are obviously more complex than just increments/decrements, so it would be nice to avoid the complexity of queueing these unless totally necessary). Ideally I would just like to be able to tell the component to take over control of (mouse) event dispatch on its children and buffer these appropriately.
I can’t find any fixes through Googling. The best match I can find is here but this is from 2019 so very possibly outdated and the main link is dead.
function App() {
// console.log("---RENDER---");
const [value, setValue] = React.useState(0);
const onMouseEnter = React.useCallback(() => {
// console.log(" incr");
setValue(value+1);
}, [ value ]);
const onMouseLeave = React.useCallback(() => {
// console.log(" decr");
setValue(value-1);
}, [ value ]);
const events = { onMouseEnter, onMouseLeave };
return (
<div>
<h1>{value}</h1>
<div style={{"background": "blue", "padding": "1rem"}} {...events}>
<div style={{"background": "red", "width": "2rem", "height": "2rem"}} {...events}>
</div>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root" />