React-select: recalculate menu top position on scroll when using `menuPosition='fixed'`

Created on 23 Jun 2019  路  7Comments  路  Source: JedWatson/react-select

Hi, thanks for having this awesome library, it has been very helpful.

There seems to be a bug when using menuPosition fixed with react select except of course I am missing something.

The menu position should be with the select box when scrolling the page vertically but its fixed and isn't recalculated on scroll.

I have reproduced it here
https://codesandbox.io/s/react-codesandboxer-example-xy47y

issubug-unconfirmed

Most helpful comment

FWIW, I currently use this hack

  handleCloseMenuOnScroll = () => {
    // forcing select menu re-rendering on scroll
    this.setState({ top: 0 });
   // as expected do not close the menu on scroll
    return false;
  }

<Select
     ...
     styles={{
         menuPortal: base => ({
            // this just updates the menuPortal style to trigger a rerender
            top: this.state.top,
            ...base,
         }),
     }}
     closeMenuOnScroll={this.handleCloseMenuOnScroll}
/>

All 7 comments

Bumped into while searching for menu. I came up with a hacky solution for an older version of react-select. Wasn't perfect, but it even handled nested scrolling by hiding the menu if the inner scroll container hid the select.

Might be of use:

    updateMenuPosition = () => {
        this.setState((prevState) => {
            const wrapper = $(this.wrapperRef);
            const menuElement = $('.Select-menu-outer');
            const isMenuOpen = menuElement.length > 0;

            let menuPositionStyling = null;
            let maxMenuHeightOffset = null;
            if (isMenuOpen) {
                const menuBoundingBox = menuElement[0].getBoundingClientRect();
                const realMenuHeight = menuBoundingBox.height;
                const unconstrainedMenuHeight = realMenuHeight - prevState.maxMenuHeightOffset;
                const scrollableContainer = wrapper.parents('.scrollable-container')[0];
                const scrollableContainerBoundingBox = scrollableContainer != null ? scrollableContainer.getBoundingClientRect() : null;
                const controlBoundingBox = wrapper.find('.Select-control')[0].getBoundingClientRect();
                const borderOffset = 2;

                menuPositionStyling = {
                    position: 'fixed',
                    top: controlBoundingBox.top + controlBoundingBox.height + borderOffset, // + 2 to go below border
                    left: controlBoundingBox.left,
                };

                // hack: safari scrolling element is <body>, other browsers is <html>. Getting/setting scrollTop on this element will make the below functionality work on both browsers while still using html element for boundaries.
                // document.scrollingElement is not defined for IE 11, so fallback to htmlElement.
                const htmlElement = document.documentElement;
                const pageScrollingElement = document.scrollingElement || htmlElement;

                const pageScrollable = htmlElement.clientHeight < htmlElement.scrollHeight;
                let heightBoundary = window.innerHeight;
                let hide = controlBoundingBox.top < 0 || controlBoundingBox.bottom > htmlElement.clientHeight;

                if (pageScrollable) {
                    const remainingScrollDistance = htmlElement.scrollHeight - pageScrollingElement.scrollTop - htmlElement.clientHeight;
                    heightBoundary += remainingScrollDistance;
                }

                if (menuPositionStyling.top + unconstrainedMenuHeight > heightBoundary) { // place dropdown on top
                    if (scrollableContainerBoundingBox != null &&
                        (controlBoundingBox.top < scrollableContainerBoundingBox.top || controlBoundingBox.top > scrollableContainerBoundingBox.bottom)) {
                        hide = true;
                    }
                    menuPositionStyling.top = controlBoundingBox.top - borderOffset - unconstrainedMenuHeight;
                    if (menuPositionStyling.top < 0) {
                        maxMenuHeightOffset = menuPositionStyling.top;
                        menuPositionStyling.top = 0;
                    }
                } else { // dropdown on bottom
                    if (scrollableContainerBoundingBox != null &&
                        (controlBoundingBox.bottom < scrollableContainerBoundingBox.top || controlBoundingBox.bottom > scrollableContainerBoundingBox.bottom)) {
                        hide = true;
                    }
                    if (!hide) {
                        const calculatedMenuBottom = menuPositionStyling.top + menuBoundingBox.height + pageScrollingElement.scrollTop;
                        if (pageScrollable && calculatedMenuBottom > htmlElement.clientHeight) {
                            pageScrollingElement.scrollTop = Math.max(calculatedMenuBottom - htmlElement.clientHeight, pageScrollingElement.scrollTop);
                        }
                    }
                }

                if (hide) {
                    menuPositionStyling.display = 'none';
                }
            }

            return {
                menuPositionStyling,
                maxMenuHeightOffset,
            };
        }, () => this.forceUpdate());
}

Had a window scroll handler call that.

Is there any update on this issue?

FWIW, I currently use this hack

  handleCloseMenuOnScroll = () => {
    // forcing select menu re-rendering on scroll
    this.setState({ top: 0 });
   // as expected do not close the menu on scroll
    return false;
  }

<Select
     ...
     styles={{
         menuPortal: base => ({
            // this just updates the menuPortal style to trigger a rerender
            top: this.state.top,
            ...base,
         }),
     }}
     closeMenuOnScroll={this.handleCloseMenuOnScroll}
/>

hi, did anyone find a way to fix it?

hi, did anyone find a way to fix it?

@HodayaGruz have you tried https://github.com/JedWatson/react-select/issues/3646#issuecomment-531510221 above ?

@xmile1 i tried, the menu is stick to the top of the page

Hello -

Thanks for reporting this issue.

This bug has been reported multiple times as per issue #4088.

A new master issue #4088 has since been created to keep track this this bug.

This new issue will exist as the source of truth going forward to investigate the issue, report findings, and implement a bug fix.

We'll take into account all the details above while investigating.

If you feel this issue has been wrongly closed as it isn't related to the new master issue #4088, please let us know and we'll take another look.

Thank you!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

juliensnz picture juliensnz  路  3Comments

yrabinov picture yrabinov  路  3Comments

x-yuri picture x-yuri  路  3Comments

MalcolmDwyer picture MalcolmDwyer  路  3Comments

mjuopperi picture mjuopperi  路  3Comments