I have a Card div that is supposed to show the scroll if the content exceeds it's height. I've used overflow-y: auto to do that. I'm trying to use a Select inside it, and the select menu is supposed to show in front of the card. The menu position is absolute.
The problem is, even with position: absolute, the menu is taking space inside the card. making it scrollable.

If I remove the overflow from the card, it works fine, but the content exceeds it. I've created a sandbox for it:
https://codesandbox.io/s/position-absolute-inside-overflow-y-9kppcy?file=/src/App.js
Show the SelectMenu inside a portal.
Remove the overflow from the card, add it to a CardBody element, and keep the select outside it.
This might be a common question, but I could not find a solution.
I think your solution of using a React Portal actually makes a lot of sense, but you just need a little more trick to position the element.
{showMenu &&
createPortal(
<div className="SelectMenu" style={menuStyle}>
I'm the menu.
</div>,
PORTAL_HOST_NODE
)}
The menuStyle is simply a computed value returned by useMemo, which is simply the CSS styles we need to apply to position your menu. What you want is the menu to use position: fixed, and then simply use the top, left, width and height attributes of the trigger element (in this case, your "Open Select" button) to position the element.
In order to ensure that the position is updated when the viewport is scrolled, you will also need to track the window.scrollY value:
const [scrollY, setScrollY] = useState(window.scrollY);
useEffect(() => {
const onScroll = () => setScrollY(window.scrollY);
window.addEventListener("scroll", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
};
}, []);
I see that you have a manual 0.5rem offset you want for the select menu to be positioned away from its trigger element. We can store in as a CSS variable:
.SelectMenu {
--top-offset: 0.5rem;
position: fixed;
top: 0;
left: 0;
z-index: 99;
border: 1px solid #bebebe;
background-color: white;
border-radius: 0.5rem;
padding: 2rem 1rem;
box-sizing: border-box;
}
Then, it is matter of setting the other code up as mentioned before. Some notes:
const [menuTriggerElementRect, setMenuTriggerElementRect] = useState(null);
const toggleMenu = useCallback((e) => {
setShowMenu((old) => !old);
setMenuTriggerElementRect(e.currentTarget.getBoundingClientRect());
}, []);
const menuStyle = useMemo(() => {
if (menuTriggerElementRect === null) return {};
const { top, left, width, height } = menuTriggerElementRect;
return {
transform: `translate(${left}px, calc(${
top + height - scrollY
}px + var(--top-offset)))`,
width: `${width}px`
};
}, [menuTriggerElementRect, scrollY]);
See your updated CodeSandbox example here:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With