shadcn-vue icon indicating copy to clipboard operation
shadcn-vue copied to clipboard

[Bug]: Combobox - Maximum recursive updates exceeded

Open lassesbrt opened this issue 11 months ago • 8 comments

Reproduction

https://stackblitz.com/edit/is2sf6?file=src%2FApp.vue

Describe the bug

Combobox is unable to render more than 100 elements. This can be easily reproduced when altering any combobox example from the docs to show more than 100 elements at a time.

Opening the popover results in the <CommandEmpty> fallback to be displayed.

When opening the popover, this error message gets thrown in the browser console: Maximum recursive updates exceeded. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.

I have read a bit about it and this radix-vue issue seems related: https://github.com/radix-vue/radix-vue/issues/551

System Info

System:
    OS: macOS 14.2
    CPU: (10) arm64 Apple M1 Pro
    Memory: 73.25 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 18.17.0 - ~/.nvm/versions/node/v18.17.0/bin/node
    npm: 9.6.7 - ~/.nvm/versions/node/v18.17.0/bin/npm
  Browsers:
    Chrome: 122.0.6261.94
    Firefox: 119.0
    Safari: 17.2
  npmPackages:
    @vueuse/core: ^10.9.0 => 10.9.0 
    radix-vue: ^1.4.9 => 1.4.9 
    vue: ^3.4.19 => 3.4.19

Contributes

  • [ ] I am willing to submit a PR to fix this issue
  • [ ] I am willing to submit a PR with failing tests

lassesbrt avatar Mar 03 '24 00:03 lassesbrt

I am also having the same issue. Is there a workaround?

a1danw avatar Mar 19 '24 15:03 a1danw

I have this issue as well. I figured that until 100 items go fine, everything above 100 items will cause this issue 🤔

Check the example: https://stackblitz.com/~/github.com/devbyray/shadcn-vue-combobox-issue

devbyray avatar Apr 03 '24 19:04 devbyray

I'm also having this issue, I have ~100/200 items

KeziahMoselle avatar Apr 13 '24 15:04 KeziahMoselle

I'm also having this issue, 300 items

lazbeta avatar Apr 25 '24 08:04 lazbeta

same problem here. works for under ~100 items

rasco avatar Apr 25 '24 09:04 rasco

It looks like it is fixed 💪🥳

devbyray avatar Apr 29 '24 07:04 devbyray

With the latest version of radix, it no longer shows this error but it's still VERY slow for larger lists. I think ListboxVirtualizer may fix the issue but I don't know if it's directly usable here.

8uff3r avatar Apr 29 '24 10:04 8uff3r

the fix works for me! maybe you can try this as workaround Screenshot 2024-04-29 at 13 50 34

lazbeta avatar Apr 29 '24 11:04 lazbeta

With the latest version of radix-vue (1.8.5) the issue is back: https://stackblitz.com/edit/is2sf6-qsxvsu?file=src%2FApp.vue The error message is slightly different: Uncaught (in promise) Maximum recursive updates exceeded in component <ComboboxInput>. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.

When you comment out the <CommandInput> the combobox works fine.

Any ideas?

felox2 avatar Jun 26 '24 09:06 felox2

I have the same issue in v1.9.0

phuclh avatar Jul 09 '24 03:07 phuclh

a weird fix, for those using nuxt 3, go to CommandInput.vue and wrap the whole thing btween a ClientOnly component, like this:

<ClientOnly>
    <div class="flex items-center border-b px-3" cmdk-input-wrapper>
      <Search class="mr-2 size-4 shrink-0 opacity-50" />
      <ComboboxInput
        v-bind="{ ...forwardedProps, ...$attrs }"
        auto-focus
        :class="cn('flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)"
      />
    </div>
  </ClientOnly>

somehow this fixes it and it works with larger arrays of items

Flowko avatar Jul 10 '24 19:07 Flowko

a weird fix, for those using nuxt 3, go to CommandInput.vue and wrap the whole thing btween a ClientOnly component, like this:

<ClientOnly>
    <div class="flex items-center border-b px-3" cmdk-input-wrapper>
      <Search class="mr-2 size-4 shrink-0 opacity-50" />
      <ComboboxInput
        v-bind="{ ...forwardedProps, ...$attrs }"
        auto-focus
        :class="cn('flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)"
      />
    </div>
  </ClientOnly>

somehow this fixes it and it works with larger arrays of items

Do you have a fix for Vite?

phuclh avatar Jul 10 '24 19:07 phuclh

@phuclh maybe try to add const isBrowser = typeof window !== 'undefined'; and add it to the parent div as v-if="isBrowser" didnt test it, just speclating that this what clientOnly does in nuxt 3

Flowko avatar Jul 10 '24 19:07 Flowko

I have 1000 entries, getting same error, so I implemented solution provided by @lazbeta for now. Probably would be optimized to implement async search for larger datasets, but I am not getting any lags while rendering the list (tested on local machine).

NOTE: using Inertia.js, not Nuxt

In script setup:

const usersComboboxOpen = ref(false);
const usersSearchTerm = ref("");
const users = usePage().props.users;
const userEntriesLimit = 50;
const filteredUsers = computed(() => {
  if (usersSearchTerm.value.length < 3) {
    return users.slice(0, userEntriesLimit);
  }

  const filtered = users.filter((user) =>
    user.label.toLowerCase().includes(usersSearchTerm.value.toLowerCase())
  );

  if (filtered.length > userEntriesLimit) {
    return filtered.slice(0, userEntriesLimit);
  }

  return filtered;
});

In template:

<Popover v-model:open="usersComboboxOpen">
  <PopoverTrigger as-child>
    <Button
      variant="outline"
      role="combobox"
      :aria-expanded="usersComboboxOpen"
      class="justify-between px-3"
    >
      {{
        form.user_id
          ? users.find((user) => user.value === form.user_id)?.label
          : "Select user..."
      }}
      <ChevronsUpDownIcon class="ml-2 size-4 shrink-0 opacity-50" />
    </Button>
  </PopoverTrigger>
  <PopoverContent class="p-0">
    <Command v-model:searchTerm="usersSearchTerm">
      <CommandInput
        class="h-9"
        placeholder="Search user..."
      />
      <CommandEmpty>No users found.</CommandEmpty>
      <CommandList
        <CommandGroup>
          <CommandItem
            v-for="user in filteredUsers"
            :key="user.value"
            :value="user.label"
            @select="
              () => {
                form.user_id = user.value;
                usersComboboxOpen = false;
              }
            "
          >
            {{ user.label }}
            <CheckIcon
              :class="
                cn(
                  'ml-auto size-4 shrink-0',
                  form.user_id === user.value
                    ? 'opacity-100'
                    : 'opacity-0'
                )
              "
            />
          </CommandItem>
        </CommandGroup>
      </CommandList>
    </Command>
  </PopoverContent>
</Popover>

kikky7 avatar Jul 28 '24 12:07 kikky7

Seems it's working good in v1.9.3, and it should be rendered better in v2

Closing as a stale issue

sadeghbarati avatar Aug 08 '24 18:08 sadeghbarati

I confirm it works with latest (1.9.5) version. Problem was in 1.9.1 for me. I tried with 1000 and 250 items and both work. The 1000 one has some minor "lag" so I still prefer the solution above which limits the entire list. 250 items is instant render.

kikky7 avatar Sep 04 '24 13:09 kikky7