Skip to content

Instantly share code, notes, and snippets.

@kumarharsh
Last active October 7, 2022 21:02
Show Gist options
  • Save kumarharsh/5ada99a2f6b5867984991b70bdc453a1 to your computer and use it in GitHub Desktop.
Save kumarharsh/5ada99a2f6b5867984991b70bdc453a1 to your computer and use it in GitHub Desktop.
Shows how to use react-select with portals provided by react-overlay
import ReactSelect from 'react-select';
import Portal from 'react-overlays/lib/Portal';
import 'react-select/dist/react-select.css'; // I'm using css-modules, but you can use whatever you like
import css from './Select.css';
import defaultMenuRenderer from 'react-select/lib/utils/defaultMenuRenderer'; // this renders the actual menu - we can reuse the same component
class _SelectMenu extends React.Component {
props: { // flow types
selectProps: {},
selectDOM: HTMLElement,
};
componentDidMount() {
window.addEventListener('scroll', this._handleScroll); // just forceUpdate on scroll
}
componentWillUnmount() {
window.removeEventListener('scroll', this._handleScroll);
}
getDocumentBody() {
return document.body;
}
_handleScroll = () => {
this.forceUpdate(); // just need to re-render on scroll so that the menu is not stuck in mid-air while the select input scrolls off-screen
}
render() {
const rect = this.props.selectDOM.getBoundingClientRect();
const computedStyle = window.getComputedStyle(this.props.selectDOM); // inherit as many styles as you want from the parent select
const fontSize = computedStyle.getPropertyValue('font-size');
const lineHeight = computedStyle.getPropertyValue('line-height');
const position = {
top: rect.bottom - 1,
left: rect.left,
width: rect.width,
};
return (
<Portal container={this.getDocumentBody}>
<div className={cx(css.selectMenuPortal, 'Select-menu-outer')} style={{...position, fontSize, lineHeight}}>
<div className="Select-menu">{ defaultMenuRenderer(this.props.selectProps) }</div>
</div>
</Portal>
);
}
}
export default class Select extends React.Component {
... other stuff ...
_selectDOM: HTMLElement = null;
componentDidMount() {
this._selectDOM = findDOMNode(this._select).children[0];
}
componentDidUpdate() {
this._selectDOM = findDOMNode(this._select).children[0];
}
_getRef = (c) => { this._select = c; }
_renderMenu = (selectProps) => {
if (!this._selectDOM) {
return defaultMenuRenderer(selectProps);
}
return (
<_SelectMenu
selectProps={selectProps}
selectDOM={this._selectDOM} // pass the select's dom node (for getting styles & bounding-box calcs
/>
);
}
render() {
const {
wrapperClassName,
...otherProps,
} = this.props;
return (
<div
ref={this._getRef} // I am using some custom styles, so using a wrapping div. You may use <ReactSelect ...> directly here.
className={cx(
css.container,
wrapperClassName,
)}
>
<ReactSelect
{...otherProps}
menuRenderer={this._renderMenu}
/>
</div>
);
}
}
@jeffmcaffer
Copy link

@kumarharsh, thanks for the example. Can you throw a license (e.g., MIT) on this just to make it clear if/how folks can use it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment