React-select: Dropdown goes outside of screen if used on either left or right side of window.

Created on 29 Mar 2019  路  15Comments  路  Source: JedWatson/react-select

I am using react-select to be on right end of the window. I've no other option to relocate it to any where else. When I click on it the dropdown goes outside of the window and it looks as below image.

image

Is there any work around or a prop available to avoid this kind of situations.

issubug-unconfirmed issureviewed

Most helpful comment

@bladey, @Rall3n Here is the example of reproduction the issue: https://codesandbox.io/s/react-select-v3-sandbox-6kcol?file=/example.js
We have dropdown on the right side of the page, also with fixed width and want to open options in one line. We expect the options menu to align on the right side.

All 15 comments

Any update on this issue? Im having the same problem guys

+1

Could anyone provide a minimal reproducible example, and the versions of React and react-select?

The current issue is still exist (even in the latest version 3.1.0). It would be great if you took a look on it.

@SuperMari0o Could you provide a minimal reproducible example of the issue?

@bladey, @Rall3n Here is the example of reproduction the issue: https://codesandbox.io/s/react-select-v3-sandbox-6kcol?file=/example.js
We have dropdown on the right side of the page, also with fixed width and want to open options in one line. We expect the options menu to align on the right side.

This would definitely be nice. Having the same issue right now as well.
Anyone have a work around?

+1

@bladey, @Rall3n Will this ticket be in progress, and should we wait for the desired behavior for the @SuperMari0o example (https://github.com/JedWatson/react-select/issues/3491#issuecomment-654706668)?

I've started a fork of react-select. Feel free to resubmit this issue on the fork and/or submit a PR to resolve this issue on the fork and we can get it merged and released.

EDIT: :tada: I've archived the fork now that we've got some momentum in this repo and Jed is involved again. Sorry for the disturbance!

@SuperMari0o

We have dropdown on the right side of the page, also with fixed width and want to open options in one line. We expect the options menu to align on the right side.

Maybe I am missing something, but if you want the menu to align on the right side, then why not just position the Menu: right: 0?

Working demo: codesandbox

const styles = {
  menu: () => ({
    position: "absolute",
    right: 0
  })
}

return <Select styles={styles} {...otherProps} />

This seems to be working. If no one has objections on this I will close this out, but in case I am missing something, please let me know and I can re-open this.

Thank you for your advice but in our case, we have a floating layout and we don't know where the dropdown will appear.

@aleksey-selyavkin

A menu by default is set to constrain the width of the menu to the same width as the Select to avoid this.

In the provided example, the Option has white-space: nowrap preventing line-wrapping and thus causing the MenuList to be wider than expected. Because this width is wider than the Select, the positioning no longer appears to work. I suspect that is what is happening as well in your case or the original posters case, (though I have no idea what I am looking at in that screenshot because of the cropping).

How can you solve this?
Ensure that the width of your options and menuList are not touched so that they naturally fit in the assigned width of the menu.

What if you want a bigger menu than your select?
If someone is going to be responsible for putting the Menu in a portal, increasing the width beyond the default setting, and then moving that portal target anywhere between the left to right edges of the screen, then it seems reasonable that the user needs to be responsible for positioning the menu.

Fortunately, the styles api provides you this kind of flexibility, but not without having to do the calculation to determine where it should go. So, how do you calculate this?

Not sure it's worth the effort to you, but you'd likely want to use an IntersectionObserver,

  1. create a ref or use a class variable.
  2. onMenuOpen, setTimeout 1ms, getMenuListRef from selectRef, create callback function for Intersection observer, set ref to intersectionobserver with callback
  3. IntersectionCallback should compare to ensure intersectionRect.width >= boundingClientRect.width and if not, set a state variable indicating alignMenuRight
  4. use state variable in styles api to conditionally add right: 0 to menu style
  5. onMenuClose, remove intersectionobserver (assuming ref is still available which it might not be) and clear ref

I could and still may put up a codesandbox example, but this is likely how I would approach it. This is a lot of heavy lifting for an edge case which seemingly can be avoided. I am also willing to consider that there may be simpler ways to identify if a component has wrapped to a new line and should be positioned left or right.

I hope this answers your question or concern, but end of the day, just make sure the option width isn't being stretched beyond the menu width and this should be avoidable. It is possible I am missing something and there might be other use cases, so feel free to include a codesandbox and I can take a look.

@aleksey-selyavkin
Here is a working solution for edge detection using IntersectionObserver. I'd likely use some hide/show mechanism to avoid the UX visual experience of the edge snapping taking place, but that's fairly straightforward.

Working demo: codesandbox

const EdgeAligningMenuSelect = (props) => {
  const [alignRight, setAlignRight] = useState(false);

  const menuObserver = useRef({});
  const selectRef = useRef();

  const onMenuOpen = () => {
    const observeOnscreen = (entries = []) => {
      const { boundingClientRect, intersectionRect } = entries[0];
      const isOffscreen = boundingClientRect.width < intersectionRect.width;
      setAlignRight(!isOffscreen);
    };

    setTimeout(() => {
      const menuList = selectRef.current.select.menuListRef;
      //console.log("menu opened", menuList);

      menuObserver.current = new IntersectionObserver(observeOnscreen);
      menuObserver.current.observe(menuList);
      //console.log(menuObserver.current);
    }, 1);
  };

  const onMenuClose = () => {
    //console.log("menu closed");

    setAlignRight(false);
    menuObserver.current.disconnect();
    //console.log(menuObserver.current);
  };

  const customStyles = {
    menu: () => ({
      position: "absolute",
      boxShadow: "0 20px 54px 0 rgba(0,0,0,0.5)",
      top: "2.5rem",
      ...(alignRight && { right: 0 })
    }),
    option: (provided) => ({
      ...provided,
      whiteSpace: "nowrap"
    })
  };

  return (
    <div style={{ width: "150px" }}>
      <Select
        ref={selectRef}
        menuPortalTarget={document.getElementById("portal")}
        className="basic-single"
        classNamePrefix="select"
        name="color"
        options={stateOptions}
        styles={customStyles}
        menuPlacement="auto"
        menuShouldBlockScroll={false}
        onMenuOpen={onMenuOpen}
        onMenuClose={onMenuClose}
      />
    </div>
  );
};

@ebonow Many thanks for your effort.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pashap picture pashap  路  3Comments

geraldfullam picture geraldfullam  路  3Comments

Meesam picture Meesam  路  3Comments

steida picture steida  路  3Comments

mbonaci picture mbonaci  路  3Comments