JedWatson / react-select

The Select Component for React.js
https://react-select.com/
MIT License
27.68k stars 4.13k forks source link

Multi-select scrolls to top after selecting (v.1.0.0) #977

Closed steinso closed 4 years ago

steinso commented 8 years ago

Problem: After selecting an option in a multi-select enabled component, the option menu automatically scrolls to the top.

I have debugged this a bit, and it looks like the reason is that on render it wants to focus the selected option, however in multi-select mode, that option is removed from the option menu. This makes it default to the first element in the menu.

5th line in render function, and this.getFocusableOption.

One way to go about this could be to find the adjacent option in the props.options list, and then find the same option in the _visibleOptions list.

prateekjahead commented 8 years ago

Facing the same issue. I think an expected behaviour would be to maintain the scroll position of the menu on selecting an option and just pull up the remaining options in the list.

dsoriano commented 8 years ago

+1 If the PR of @steinso is OK, is it possible to merge ?

FourwingsY commented 8 years ago

+1

b6pzeusbc54tvhw5jgpyw8pwz2x6gs commented 7 years ago

+1

BossGrand commented 7 years ago

+1

swiharta commented 6 years ago

Maybe it's been fixed in the meantime, but for me in multi mode it's actually scrolling to and highlighting the option immediately following to the last selected option (which is removed from options in multi mode), which I assume is the desirable behavior.

But, I wish there was at least a setting to always leave the scroll position at the top when opening the menu, whether multi-select or single. I have no interest in retaining the scroll position of the last selected item for my use case, and the best way I have found to eliminate this is to edit the source code, in componentDidUpdate, I just commented out the first if statement block...

        key: 'componentDidUpdate',
        value: function componentDidUpdate(prevProps, prevState) {
            // focus to the selected option
            if (this.menu && this.focused && this.state.isOpen && !this.hasScrolledToOption) {
                var focusedOptionNode = _reactDom2.default.findDOMNode(this.focused);
                var menuNode = _reactDom2.default.findDOMNode(this.menu);
                menuNode.scrollTop = focusedOptionNode.offsetTop;
                this.hasScrolledToOption = true;
            } else if (!this.state.isOpen) {
                this.hasScrolledToOption = false;
            }

If there was an option to disable scrolling (and highlighting), the last selected option (which ends up being the next adjacent option in multi mode), it would be great.

vitaliikobrin commented 5 years ago

Hi everyone!

I'm still facing the issue with scroll. I'm using V2 of the component and as I see everything has changed. I cannot find the above code anymore. Do somebody deal with it in 2019? Maybe I missed some component option. Or there is another part of code caused the issue.

This is critical for me as I'd like to keep multi-select opened while a user selects options. The idea is to close the dropdown when a user clicks on submit button inside it. But the dropdown scrolls up every time a user selects an option and this makes the idea totally unusable.

Is it possible to fix the issue on my side?

hogiyogi597 commented 5 years ago

+1

yashug commented 5 years ago

@vitaliikobrin did you found a solution to this? I am also facing the same issue

pbeleckis commented 4 years ago

Any news on this issue? Is there a some kind of workaround?

Gazpa commented 4 years ago

Experiencing the same issue. In my case I use the multi option and a custom menu selection popup. On the first click anywhere in the popup container it will scroll to top in case the input is not visible in the page.

aliakbarazizi commented 4 years ago

+1

aliakbarazizi commented 4 years ago

@bladey why not confirmed? this is a huge bug

bladey commented 4 years ago

Hi @aliazizi, thanks for your feedback.

Can you please confirm this issue is still affecting v3 of react-select?

In an effort to sustain the react-select project going forward, we're cleaning up and closing old issues using v1 and v2 of react-select.

In the best interest of supporting the broader community we have to direct our efforts towards the current major version v3 and beyond.

alikrodrigues commented 4 years ago

Hi @bladey , I'm not the author.. but I can confirm this issue in 3.1.0.

Gravação de Tela 2020-06-15 às 11 29 58

bladey commented 4 years ago

Thanks @alikrodrigues, I appreciate the feedback.

bladey commented 4 years ago

@alikrodrigues I just tweaked the sandbox example and I'm unable to reproduce the issue, could you let me know what I'm missing from my config (it also works when searching and selecting).

MLoom5

https://codesandbox.io/s/react-codesandboxer-example-ewd44

alikrodrigues commented 4 years ago

Hi @bladey thanks for answered, When I use a wrapMenuList to show the selected the focus will redirect to MenuList.. When I removed and show just the badges the work it's ok.. Gravação-de-Tela-2020-06-16-às-11 39 53

Here is my Select Component image

and here is my MenuList image

[edit]:
My versions image

aliakbarazizi commented 4 years ago

@bladey I reproduce the problem

You can see it here

https://codesandbox.io/s/react-codesandboxer-example-5xmq0?file=/index.js

The problem happen when override MenuList component with hideSelectedOptions and closeMenuOnSelect set to false

Can you confirm this bug now?

Update: The poblem also exist when overwirte Menu component

ghost commented 4 years ago

Running into this issue also, I have a custom Menu Item component and closeMenuOnSelect set to false. Have you found a workaround to this issue or is this just a bug that can't be solved

Rall3n commented 4 years ago

@aliazizi Common mistake with Reacts render method: You are defining your MenuList component inside the render method. As this method is called on every change to the components state, the MenuList component is also redefined every time, which causes the HTML element to be replaced and the scroll-to-top to happen.

Solution: Move your component outside of the render method.

const MenuList = () => { /* ... */ };

/* ...*/

render() {
    return <Select components={{ MenuList }} />;
}
bladey commented 4 years ago

Thanks for the answer @Rall3n!

I can confirm this works, I modified the Codesandbox here - https://codesandbox.io/s/react-codesandboxer-example-4mj9p?file=/index.js

The problem no longer exists for me @aliazizi and @calvinherd9, let me know.

aliakbarazizi commented 4 years ago

@Rall3n! thanks, it's working

@bladey thank you it's working now

alikrodrigues commented 4 years ago

Thanks guys, It's working for me too.

Texetomg commented 3 years ago

@Rall3n I spent 2 days off looking for a solution and trying to fix this bug. how everything turned out to be trite. thanks !

tylerlwsmith commented 2 years ago

A note for anyone else who was struggling with this: React Select seems to use object identity matching under the hood to determine which values are selected. That means if you programmatically generate your {label, value} object every render, React Select will have trouble keeping track of it.

For example: the following code will cause React Select to jump to the top after every selection, because different objects are passed to React Select every render (even though they have the same values):

import React { useEffect, useState, useMemo } from "react";
import Select from "react-select";
import axios from "axios";

function BlogPostSelect() {
  const [posts, setPosts] = useState(null);

  useEffect(function getPosts() {
    axios.get("/api/posts/)
      .then(({data}) => {
        setPosts(data);
      })
  }, []);

  // New objects are created every render.
  function makeSelectOptions() {
    return posts.map(post => ({ label: post.title, value: post.id }))
  }

  if (!posts) return;
  return (
    <Select
      multi={true}
      closeMenuOnSelected={false}
      hideSelectedOptions={false}
      options={makeSelectOptions()}
  )
}

On the other hand, if we computed the list once with useMemo, the objects would keep their identities, and React Select would maintain its position and not jump to the top after you select an item.

import React { useEffect, useState } from "react";
import Select from "react-select";
import axios from "axios";

function BlogPostSelect() {
  const [posts, setPosts] = useState(null);

  useEffect(function getPosts() {
    axios.get("/api/posts/)
      .then(({data}) => {
        setPosts(data);
      })
  }, []);

  // This is now memoized, so objects maintain their identity.
  const selectOptions =  useMemo(() => {
    return posts.map(post => ({ label: post.title, value: post.id }))
  }, [posts])

  if (!posts) return;
  return (
    <Select
      multi={true}
      closeMenuOnSelected={false}
      hideSelectedOptions={false}
      options={selectOptions}
  )
}

Hopefully this helps someone in the future. I didn't actually run the code above so there may be a couple of typos, but this general concept got this working for me.

ektak26 commented 2 years ago

In my case, I want to send some callback functions [onChange] and properties in the menu list component but when I pass them somehow scroll is not working [when the item is selected it scrolls to the top]

I modified the Codesandbox here https://codesandbox.io/s/react-codesandboxer-example-forked-s0320q?file=/index.js

can anyone please help me with this? thanks

tk21 commented 2 years ago

@ektak26

In your example, it looks like you are still returning a new MenuList on every render with how you have MenuList: (props) => ..... If you define it without a function that gets called on every render, then it works. Take a look at this fork of your sandbox: https://codesandbox.io/s/react-codesandboxer-example-forked-t1fytw?file=/index.js

The main thing is that I defined const customComponents = { MenuList: MenuListCustom }; outside of the component (or you can wrap it in a useMemo if you want) and then for your Select component I have the prop:

<Select
        ... other props
        components={customComponents}
      />
vijay1479 commented 1 year ago

Please use useCallback with MenuList. That will work. const MenuList = useCallback((props: any) => { return (

{props.children}
);

}, []);

alexalannunes commented 1 year ago

@vijay1479 you saved my life!

marcos-smartflow commented 1 year ago

@ektak26

In your example, it looks like you are still returning a new MenuList on every render with how you have MenuList: (props) => ..... If you define it without a function that gets called on every render, then it works. Take a look at this fork of your sandbox: https://codesandbox.io/s/react-codesandboxer-example-forked-t1fytw?file=/index.js

The main thing is that I defined const customComponents = { MenuList: MenuListCustom }; outside of the component (or you can wrap it in a useMemo if you want) and then for your Select component I have the prop:

<Select
        ... other props
        components={customComponents}
      />

what if I need to add a button to change the state at the top of the list? still looking for a solution, I have something like that, and it keeps scrolling to the top as soon as I select an option

const MenuList = (props: any) => {
    return (
      <components.MenuList {...props}>
        {selectedAccounts.length > 0 ? (
          <button className="btn btn-link" onClick={() => setSelectedAccounts([])}>
            deselect all accounts
          </button>
        ) : (
          <button className="btn btn-link" onClick={() => setSelectedAccounts(accounts)}>
            select all accounts
          </button>
        )}
        {props.children}
      </components.MenuList>
    )
  }
amishaaggarwal commented 2 months ago

i am using a custom menuList with customFilter placed above the ul and the menu scrolls to top as soon as I select any option in isMulti mode...is there a solution to this problem?