Skip to content

Commit a4a0ea5

Browse files
Ensure we are not freezing data when the static prop is used (#3779)
This PR fixes an issue where a listbox with the `static` option had a delayed `selected` value. This was happening because of 2 reasons: 1. The `isSelected` function had stale data 2. We use a "frozen value", but we shouldn't do that when the listbox options are using the `static` option. The frozen data is used to prevent UI jumps when you have transitions. For example, if you fade out the listbox when closing then if you select an option the selected state (and a checkmark for example) would move around to the newly selected option while the listbox is fading out. To prevent that we "freeze" the selected value until the listbox is fully closed. This behaves the same as a native select element. Fixes: #3778 ## Test plan Tested the fix with the reproduction from the issue: https://github.com/user-attachments/assets/fbbc2ec3-321b-4e66-b0fc-df5a483dfbba --------- Co-authored-by: Jordan Pittman <[email protected]>
1 parent 2de2779 commit a4a0ea5

File tree

3 files changed

+19
-5
lines changed

3 files changed

+19
-5
lines changed

packages/@headlessui-react/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
- Nothing yet!
10+
### Fixed
11+
12+
- Ensure we are not freezing data when the `static` prop is used ([#3779](https://github.com/tailwindlabs/headlessui/pull/3779))
1113

1214
## [2.2.7] - 2025-07-30
1315

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,14 +1332,20 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
13321332
// We should freeze when the combobox is visible but "closed". This means that
13331333
// a transition is currently happening and the component is still visible (for
13341334
// the transition) but closed from a functionality perspective.
1335-
let shouldFreeze = visible && comboboxState === ComboboxState.Closed
1335+
//
1336+
// When the `static` prop is used, we should never freeze, because rendering
1337+
// is up to the user.
1338+
let shouldFreeze = visible && comboboxState === ComboboxState.Closed && !props.static
13361339

13371340
let options = useFrozenData(shouldFreeze, data.virtual?.options)
13381341

13391342
// Frozen state, the selected value will only update visually when the user re-opens the <Combobox />
13401343
let frozenValue = useFrozenData(shouldFreeze, data.value)
13411344

1342-
let isSelected = useEvent((compareValue) => data.compare(frozenValue, compareValue))
1345+
let isSelected = useCallback(
1346+
(compareValue: unknown) => data.compare(frozenValue, compareValue),
1347+
[data.compare, frozenValue]
1348+
)
13431349

13441350
// Map the children in a scrollable container when virtualization is enabled
13451351
let newDataContextValue = useMemo(() => {

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -629,12 +629,18 @@ function OptionsFn<TTag extends ElementType = typeof DEFAULT_OPTIONS_TAG>(
629629
// We should freeze when the listbox is visible but "closed". This means that
630630
// a transition is currently happening and the component is still visible (for
631631
// the transition) but closed from a functionality perspective.
632-
let shouldFreeze = visible && listboxState === ListboxStates.Closed
632+
//
633+
// When the `static` prop is used, we should never freeze, because rendering
634+
// is up to the user.
635+
let shouldFreeze = visible && listboxState === ListboxStates.Closed && !props.static
633636

634637
// Frozen state, the selected value will only update visually when the user re-opens the <Listbox />
635638
let frozenValue = useFrozenData(shouldFreeze, data.value)
636639

637-
let isSelected = useEvent((compareValue: unknown) => data.compare(frozenValue, compareValue))
640+
let isSelected = useCallback(
641+
(compareValue: unknown) => data.compare(frozenValue, compareValue),
642+
[data.compare, frozenValue]
643+
)
638644

639645
let selectedOptionIndex = useSlice(machine, (state) => {
640646
if (anchor == null) return null

0 commit comments

Comments
 (0)