Skip to content

Commit 9d3b0ff

Browse files
authored
Fix Unexpected undefined crash in Combobox component with virtual mode (#3678)
This PR fixes an issue where the `Combobox` component crashes if you are using the `virtual` option and you quickly type something such that the `Combobox` opens but no valid options are available. We already check if the current active index is available in the internal `options` list. However, if you then call `virtualizer.scrollToIndex(data.activeOptionIndex)` it will crash if you are too fast. https://github.com/user-attachments/assets/f48172e6-4098-4a31-aa16-67ce21f074d1 If you are typing slowly, then it will work as expected. https://github.com/user-attachments/assets/9d522bd5-5b54-4c12-9250-a2d92a511b35 I did find an open issue on TanStack's repo about this: TanStack/virtual#879 This PR is basically a workaround by delaying the call. But it does have the expected behavior now. https://github.com/user-attachments/assets/2e5e47a5-b021-4897-b098-568711723b77 Fixes: #3583
1 parent 9a4c030 commit 9d3b0ff

File tree

3 files changed

+12
-5
lines changed

3 files changed

+12
-5
lines changed

packages/@headlessui-react/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
- Bump `@tanstack/react-virtual` to fix warnings in React 19 projects ([#3588](https://github.com/tailwindlabs/headlessui/pull/3588))
1919
- Fix `aria-invalid` attributes to have a valid `'true'` value ([#3639](https://github.com/tailwindlabs/headlessui/pull/3639))
2020
- Add missing `invalid` prop to `Combobox` component ([#3677](https://github.com/tailwindlabs/headlessui/pull/3677))
21+
- Fix `Unexpected undefined` crash in `Combobox` component with `virtual` mode ([#3678](https://github.com/tailwindlabs/headlessui/pull/3678))
2122

2223
## [2.2.0] - 2024-10-25
2324

packages/@headlessui-react/src/components/combobox/combobox.tsx

+9-3
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ function VirtualProvider(props: {
479479
children: (data: { option: unknown; open: boolean }) => React.ReactElement
480480
}) {
481481
let data = useData('VirtualProvider')
482+
let d = useDisposables()
482483
let { options } = data.virtual!
483484

484485
let [paddingStart, paddingEnd] = useMemo(() => {
@@ -528,6 +529,7 @@ function VirtualProvider(props: {
528529
}}
529530
ref={(el) => {
530531
if (!el) {
532+
d.dispose()
531533
return
532534
}
533535

@@ -537,9 +539,13 @@ function VirtualProvider(props: {
537539
}
538540

539541
// Scroll to the active index
540-
if (data.activeOptionIndex !== null && options.length > data.activeOptionIndex) {
541-
virtualizer.scrollToIndex(data.activeOptionIndex)
542-
}
542+
//
543+
// Workaround for: https://github.com/TanStack/virtual/issues/879
544+
d.nextFrame(() => {
545+
if (data.activeOptionIndex !== null && options.length > data.activeOptionIndex) {
546+
virtualizer.scrollToIndex(data.activeOptionIndex)
547+
}
548+
})
543549
}}
544550
>
545551
{items.map((item) => {

playgrounds/react/pages/combobox/combobox-virtualized.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ function Example({ virtual = true, data, initial }: { virtual?: boolean; data; i
108108
<Combobox.Options
109109
transition
110110
anchor="bottom start"
111-
className="w-[calc(var(--input-width)+var(--button-width))] overflow-auto rounded-md bg-white py-1 text-base leading-6 shadow-lg transition duration-1000 [--anchor-gap:theme(spacing.1)] [--anchor-max-height:theme(spacing.60)] focus:outline-none data-[closed]:opacity-0 sm:text-sm sm:leading-5"
111+
className="w-[calc(var(--input-width)+var(--button-width))] overflow-auto rounded-md bg-white py-1 text-base leading-6 shadow-lg transition duration-300 [--anchor-gap:theme(spacing.1)] [--anchor-max-height:theme(spacing.60)] focus:outline-none data-[closed]:opacity-0 sm:text-sm sm:leading-5"
112112
>
113113
{({ option }) => {
114114
return (
@@ -157,7 +157,7 @@ function Example({ virtual = true, data, initial }: { virtual?: boolean; data; i
157157
<Combobox.Options
158158
transition
159159
anchor="bottom start"
160-
className="w-[calc(var(--input-width)+var(--button-width))] overflow-auto rounded-md bg-white py-1 text-base leading-6 shadow-lg transition duration-1000 [--anchor-gap:theme(spacing.1)] [--anchor-max-height:theme(spacing.60)] focus:outline-none data-[closed]:opacity-0 sm:text-sm sm:leading-5"
160+
className="w-[calc(var(--input-width)+var(--button-width))] overflow-auto rounded-md bg-white py-1 text-base leading-6 shadow-lg transition duration-300 [--anchor-gap:theme(spacing.1)] [--anchor-max-height:theme(spacing.60)] focus:outline-none data-[closed]:opacity-0 sm:text-sm sm:leading-5"
161161
>
162162
{timezones.map((timezone, idx) => {
163163
return (

0 commit comments

Comments
 (0)