Skip to content

Instantly share code, notes, and snippets.

@VanTanev
Last active September 26, 2024 23:16
Show Gist options
  • Save VanTanev/bed8bdea27924ae3d2f1710b296067b6 to your computer and use it in GitHub Desktop.
Save VanTanev/bed8bdea27924ae3d2f1710b296067b6 to your computer and use it in GitHub Desktop.
// optimized, only this component rerenders on mouse enter/leave and on ctrl press,
// its children do not
export function ReactToHoverWithCtrl() {
const isCtrlPressed = useIsCtrlPressed() // assume this uses sync external store
const [isHovered, setIsHovered] = useState(false)
// this must be computed here, and eg inside useMemo()
const isCtrlAndHover = isCtrlPressed && isHovered;
// Can alternatively be done with React.memo on the expensive components,
// but I kind of prefer this approach, we now keep all the complexity in one component
// without needing to spread React.memo throughout the codebase.
//
// In any case, the whole tree below this component is now rerendered only
// if the (isCtrlPressed && isHovered) condition changes, and the rerender of this
// component itself should be basically free
const children = useMemo(() => {
if (isCtrlAndHover) {
return <SomeExpensiveComponent />
} else {
return <OtherExpensiveComponent />
}
}, [isCtrlAndHover])
return <div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
{/* below can be omitted if we're not doing SSR */}
ref={useCallback((node) => {
if (!node) return
let current = document.querySelector(":hover")
if (!current) return
do {
if (node === current) {
setIsHovered(true)
return
}
} while ((current = current.parentNode))
}, [])}>{children}</div>
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment