How do I add an onScroll handler to the menuList of a react-select component?

Viewed 1125

I would like to add an onScroll handler to a react-select component's menuList, but the following isn't working. I suspect that I need to set the onScroll for one of the children rather than an element that contains the children, but I don't know how to do that.

import React from 'react';

import Select, { components, MenuListProps } from 'react-select';
import {
  ColourOption,
  colourOptions,
  FlavourOption,
  GroupedOption,
  groupedOptions,
} from './docs/data';


const handleScroll = () =>{
  console.log('scrolling')
}

const MenuList = (
  props: MenuListProps<ColourOption | FlavourOption, false, GroupedOption>
) => {
  return (
    <components.MenuList {...props}>
      <div onScroll={handleScroll}>
        {props.children}
      </div>
    </components.MenuList>
  );
};

export default () => (
  <Select<ColourOption | FlavourOption, false, GroupedOption>
    defaultValue={colourOptions[1]}
    menuIsOpen={true}
    options={groupedOptions}
    components={{ MenuList }}
  />
);

https://codesandbox.io/s/codesandboxer-example-forked-jr74k?file=/example.tsx:0-721

I had also tried using something like this

mySelectRef.current.menuListRef.addEventListener("scroll", handleScroll)

but .current or .current.menuListRef was always undefined or null at the time when the command executed. I tried using React.useEffect and setTimeout, with poor results. I'm hoping that I can just accomplish this by setting onScroll of the right inner div.

3 Answers

You need to wrap your MenuList component with div and provide ref to this component and then access the immediate div child of this HTML component. Then you can define the onscroll function to this element.

const MenuList = (
  props: MenuListProps<ColourOption | FlavourOption, false, GroupedOption>
) => {
  const menuListRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (menuListRef.current) {
      menuListRef.current.querySelector("div").onscroll = () => {
        console.log("scrolling");
      };
    }
  }, [menuListRef]);

  return (
    <div ref={menuListRef}>
      <components.MenuList {...props}>
        <div>{props.children}</div>
      </components.MenuList>
    </div>
  );
};

Edit codesandboxer-example (forked)

Approach

I tried to find if I can get access to a native wrapper element where onScroll property is supported already. If you dig in into components documentation you can find that Menu component accepts the innerProps which are the regular HTMLAttributes every HTML element accepts. So what I did was to provide an onScroll property to innerProps.

Code

const Menu = (
  props: MenuProps<ColourOption | FlavourOption, false, GroupedOption>
) => {
  const handleScroll = () => {
    console.log("scrolling");
  };

  return (
    <components.Menu
      {...props}
      innerProps={{ ...props.innerProps, onScroll: handleScroll }}
    >
      {props.children}
    </components.Menu>
  );
};

export default () => (
  <Select<ColourOption | FlavourOption, false, GroupedOption>
    defaultValue={colourOptions[1]}
    menuIsOpen={true}
    options={groupedOptions}
    components={{ Menu }}
  />
);

Working example

Edit react-select with menu scroll

Rationale

Ideally you would not implement a custom handler from scratch, but rather reuse or start from a hook that already exists and maintained by an open source community.

When implementing it yourself, there are a few caveats like the event listener having to be passive and you having to register as well as deregister the listener using React's useEffect hook.

Examples

There are many examples out there. One could be the implementation from react-use.

Implementation

Your component could look like:

const Component = () => {
  const ref = useRef();

  const { x, y } = useScroll(ref);

  return <MyComponent ref={ref} />
}
Related