Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bug]: Very slow selection with a large amount of elements #3590

Open
2 tasks done
mr-scrpt opened this issue Apr 24, 2024 · 2 comments
Open
2 tasks done

[bug]: Very slow selection with a large amount of elements #3590

mr-scrpt opened this issue Apr 24, 2024 · 2 comments
Labels
bug Something isn't working

Comments

@mr-scrpt
Copy link

Describe the bug

I use select to output a large number of items, there can be around 1 thousand and even sometimes more than that
When I press select to get a dropdown list, on my device it takes about 3-4 seconds before the list of options appears on the screen. With native select everything works instantly

Affected component/components

Select

How to reproduce

Here is the code for my component

 <FormField
      control={control}
      name="postOffice"
      render={({ field }) => (
        <FormItem>
          <FormLabel>Post office</FormLabel>
          <Select onValueChange={field.onChange} defaultValue={field.value}>
            <FormControl>
              <SelectTrigger>
                <SelectValue placeholder="Select a verified email to display" />
              </SelectTrigger>
            </FormControl>
            <SelectContent>
              {postOfficeListToSelect &&
                postOfficeListToSelect.map((postOffice) => (
                  <SelectItem value={postOffice.value} key={postOffice.value}>
                    {postOffice.label}
                  </SelectItem>
                ))}
            </SelectContent>
          </Select>
        </FormItem>
      )}
    />

Here is what the postOfficeListToSelect array looks like

const postOfficeflistToSelect = [
  {
    value: "7b422fba-e1b8-11e3-8c4a-0050568002cf",
    type: "Branch",
    label: "Post office number 1",
  },
  {
    value: "5db422f-e1b8-11e3-8c4a-0050568002cf",
    type: "Branch",
    label: "Post office number 2",
  },
  ...,
  ...,
  ...,
  {
    value: "158db422f-e1b8-11e3-8c4a-0050568002cf",
    type: "Branch",
    label: "Post office number 1000",
  },
];

Codesandbox/StackBlitz link

No response

Logs

No response

System Info

MacBook Pro M1
Chrome, Ark

Before submitting

  • I've made research efforts and searched the documentation
  • I've searched for existing issues
@mr-scrpt mr-scrpt added the bug Something isn't working label Apr 24, 2024
@OmarAljoundi
Copy link

The slownest is coming from rendering to much HTML that any browser can handle fast, when rendering a huge list you might want to consider using some Virtualization library, the way these library works is by rendering only the needed elements within the view it self and as you scrolling it deletes the items the isn't in the view and add new HTML elements which is within the current view.

I made an example where I wanted to select an item in ### 1000 elements list:

Without Virtualization the browser had to load all the HTML content:

without.mp4

With Virtualization the browser had to load only the screen view HTML content:

with.mp4

Here is a code example:
First install react-window: npm i react-window and if you are using typescript you have to also install npm i --save-dev @types/react-window as well

Then in your Select component you need to hide scroll icon button:

const SelectContent = React.forwardRef<
  React.ElementRef<typeof SelectPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> & {
    showScrollIcon?: boolean
  }
>(
  (
    {
      className,
      children,
      position = 'popper',
      showScrollIcon = false,
      ...props
    },
    ref
  ) => (
    <SelectPrimitive.Portal>
      <SelectPrimitive.Content
        ref={ref}
        className={cn(
          'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
          position === 'popper' &&
            'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
          className
        )}
        position={position}
        {...props}
      >
        {showScrollIcon && <SelectScrollUpButton />}
        <SelectPrimitive.Viewport
          className={cn(
            'p-1',
            position === 'popper' &&
              'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
          )}
        >
          {children}
        </SelectPrimitive.Viewport>
        {showScrollIcon && <SelectScrollDownButton />}
      </SelectPrimitive.Content>
    </SelectPrimitive.Portal>
  )
)

Then in the SelectContent you need to wrap the items within "FixedSizeList" from "react-window":

 <FixedSizeList
              width={'100%'}
              height={350}
              itemCount={data.length}
              itemSize={40}
            >
              {({ index, style, isScrolling }) => (
                <SelectItem
                  value={data[index]}
                  key={data[index]}
                  style={{
                    ...style,
                  }}
                >
                  {data[index]}
                </SelectItem>
              )}
            </FixedSizeList>

I've used random data generation to genrate a 1000 items for testing the code:
const data = Array.from({ length: 1000 }, (_, index) => `Item ${index}`)

Full Code Example:

'use client'
import { useState } from 'react'
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select'
import { FixedSizeList } from 'react-window'

const data = Array.from({ length: 1000 }, (_, index) => `Item ${index}`)

export default function MyList() {
  const [value, setValue] = useState('')

  const onChange = (v: string) => {
    setValue(v)
  }

  return (
    <div className="p-16 space-y-8">
      <div>
        <h1 className="text-green-700">With Virtualization</h1>
        <Select onValueChange={onChange} defaultValue={value} open={true}>
          <SelectTrigger>
            <SelectValue placeholder="Select a verified email to display" />
          </SelectTrigger>
          <SelectContent>
            <FixedSizeList
              width={'100%'}
              height={350}
              itemCount={data.length}
              itemSize={40}
            >
              {({ index, style, isScrolling }) => (
                <SelectItem
                  value={data[index]}
                  key={data[index]}
                  style={{
                    ...style,
                  }}
                >
                  {data[index]}
                </SelectItem>
              )}
            </FixedSizeList>
          </SelectContent>
        </Select>
      </div>

      <div>
        <h1 className="text-red-500">Without Virtualization</h1>
        <Select onValueChange={onChange} defaultValue={value}>
          <SelectTrigger>
            <SelectValue placeholder="Select a verified email to display" />
          </SelectTrigger>
          <SelectContent showScrollIcon={false}>
            {data.map((i) => (
              <SelectItem value={i} key={i}>
                {i}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </div>
    </div>
  )
}

@mr-scrpt
Copy link
Author

mr-scrpt commented May 1, 2024

@OmarAljoundi Thank you, its works for me!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants