5

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.

Dan Cancro
  • 1,401
  • 5
  • 24
  • 53

3 Answers3

2

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)

Sanket Shah
  • 2,888
  • 1
  • 11
  • 22
Hassan Imam
  • 21,956
  • 5
  • 41
  • 51
  • Thanks. This achieved the stated goal. I have a few new kinks to work out. I should have mentioned that I am working on refactoring the code for the following site to use redux and React hooks and switching my top level component from class style to function style. It has code that controls the scroll position based on the scroll position of another list and other code that reacts to user scrolling of this one. It needs to compute the sticky top and bottom headings in the react-select as scroll position and input changes. https://rfr-resources.netlify.app – Dan Cancro Nov 21 '21 at 19:52
2

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

Sanket Shah
  • 2,888
  • 1
  • 11
  • 22
fgkolf
  • 910
  • 3
  • 15
  • Thanks. I don't know why but this doesn't work in my actual case. Strange. – Dan Cancro Nov 21 '21 at 19:41
  • This is very strange. For some reason, in my app, I can add an "onClick" handler but not an "onScroll" handler. I tried it with another react-select on my app and had the same problem. But in your codesandbox one it works fine. – Dan Cancro Dec 04 '21 at 20:18
  • 1
    Alrighty. I got it working by doing essentially the same thing with MenuList instead of Menu. I still don't understand why it worked as you did it in the codesandbox case but not in my case. – Dan Cancro Dec 04 '21 at 23:48
1

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} />
}
Webber
  • 4,672
  • 4
  • 29
  • 38
  • I'm not sure what to do from here. What does the event listener being passive mean and how do I make it so? – Dan Cancro Nov 21 '21 at 19:43
  • If you use a ready-made solution, like the example, you do not have to worry about these kind of implementation details. The example that I listed already uses passive under the hood. Your question is answered here: https://stackoverflow.com/questions/37721782/what-are-passive-event-listeners – Webber Nov 22 '21 at 09:33