I created a MouseContext, the goal was to play a custom animation on mouse hover.
I want if possible, only use useRef to do that, but I find it a shame to have to put a ref to any element that needs a hover action, for the entire lifespan of my app.
So I tried with manual event listener like this :
useEffect(() => {
function inOutHandler(event) {
event.preventDefault();
const cursorType = event.type === "mouseenter" ? "active" : "";
if (
window.getComputedStyle(event.target)["cursor"].split(", ")[1] ===
"pointer"
)
mouseHandler({ type: "mouseHover", cursorType: cursorType });
window.removeEventListener("mouseenter", inOutHandler);
window.removeEventListener("mouseout", inOutHandler);
}
const links = document.querySelectorAll(
"a, .button, button"
);
for (let link of links) {
link.addEventListener("mouseenter", inOutHandler);
link.addEventListener("mouseout", inOutHandler);
}
}, []);
Okay, sound's good, but when a new element is added to the DOM, I have to listen to him again and it's a mess to deal with that.
Again, I tried something, with MutationObserver :
const mutationObserver = new MutationObserver(async (mutationList, obs) => {
if (mutationList[0].addedNodes) {
function inOutHandler(event) {
event.preventDefault();
const cursorType = event.type === "mouseenter" ? "active" : "";
if (
window.getComputedStyle(event.target)["cursor"].split(", ")[1] ===
"pointer"
)
mouseHandler({ type: "mouseHover", cursorType: cursorType });
window.removeEventListener("mouseenter", inOutHandler);
window.removeEventListener("mouseout", inOutHandler);
}
const links = document.querySelectorAll(
"a, .admin .button, button"
);
for (let link of links) {
link.addEventListener("mouseenter", inOutHandler);
link.addEventListener("mouseout", inOutHandler);
}
}
});
useEffect(() => {
if (mainRef.current) {
mutationObserver.observe(mainRef.current, {
childList: true,
subtree: true,
});
return () => {
mutationObserver.disconnect();
};
}
}, [mutationObserver]);
And that's work ! But : The mutation observer is also listening for elements that disappear from the dom, so, with each slide change (for example), my listener are executed twice. And that poses a problem for me in terms of performance.
Do you guys have an advice for me to achieve that please ?
Thanks to jfriend00 :
It sounds like you may want to use event propagation where you listen for the event on a common parent instead of on the actual DOM element. That way, you can install once listener on a parent and automatically see all the child events, even as DOM elements come and go (as long as the parent you're listening on doesn't change).
I didn't know that a parent listener would take care of DOM changes. And because I check if the current element event.target is set to pointer, that works only for links and buttons, thank you !
In terms of performance, this requires testing absolutely all the elements of the app, I don't know if this is very impactful or not.
The new code :
function inOutHandler(event) {
const cursorType = event.type === "mouseenter" ? "active" : "";
if (
window.getComputedStyle(event.target)["cursor"].split(", ")[1] ===
"pointer"
)
mouseHandler({ type: "mouseHover", cursorType: cursorType });
mainRef.current.removeEventListener("mouseenter", inOutHandler);
mainRef.current.removeEventListener("mouseout", inOutHandler);
}
useEffect(() => {
if (!loader) {
mainRef.current.addEventListener("mouseenter", inOutHandler, true); // don't forget the true
mainRef.current.addEventListener("mouseout", inOutHandler, true);
}
}, [loader]);