fix(combobox): re-open menu when async items arrive after empty response#9823
fix(combobox): re-open menu when async items arrive after empty response#9823costajohnt wants to merge 4 commits intoadobe:mainfrom
Conversation
…empty response When using useComboBox with useAsyncList, if a query returns zero results the menu closes. A subsequent query that returns results fails to re-open the menu because the inputValue tracking has already been updated by the time the async items arrive. Track when the menu was auto-closed due to an empty controlled collection and re-open it when items become non-empty while the input is still focused. Reset the flag on user-initiated closes (blur, Escape, commit) so those remain permanent. Fixes adobe#9820
- Reset closedDueToEmptyControlled in revert() so Escape prevents re-opening when async items arrive after user dismissal - Add test for Escape key scenario - Add onOpenChange assertion for trigger reason on re-open - Fix minor style: use direct variable instead of destructured object literal
| expect(onOpenChange).toHaveBeenLastCalledWith(true, 'input'); | ||
| }); | ||
|
|
||
| it('should not re-open after user dismisses with Escape (revert)', function () { |
There was a problem hiding this comment.
This test and the one below appear to have been passing before the change, where did they come from?
They also passed in the component level, which I've pushed up here as well
There was a problem hiding this comment.
You're right, both of those pass without the change too. The original thinking was they'd serve as guard rails for the re-open logic boundaries (Escape clearing the flag, uncontrolled items being excluded), but since they assert the default state (menu stays closed) they pass vacuously whether or not the re-open behavior exists.
Happy to remove them. The component-level tests you added cover the same scenarios, and the useAsyncList integration test is a much better representation of the actual user flow this fix is targeting.
Want me to drop those two hook-level tests and keep just the one that actually demonstrates the fix (controlled items going from empty to non-empty)?
Summary
Fixes #9820
When using
useComboBoxwithuseAsyncList, if a query returns zero results (e.g. typing "luka" against a Star Wars API), the menu closes. Subsequent queries that return results (e.g. backspacing to "luk" which matches "Luke Skywalker") fail to re-open the menu because theinputValuetracking has already been updated by the time the async items arrive.The root cause is an asymmetry between the
open()function (which has a|| props.itemsguard to allow opening with controlled empty collections) and the auto-close check in theuseEffect(which closes unconditionally whenfilteredCollection.size === 0).The fix tracks when the menu was auto-closed due to an empty controlled collection and re-opens it when items become non-empty while the input is still focused. The flag is reset on user-initiated closes (blur, Escape, commit) so those remain permanent.
Note: the
@react-spectrum/comboboxcomponent already works around this by auto-settingallowsEmptyCollection: truewhenloadingStateis provided, but direct hook users (useComboBox+useComboBoxState) and RAC ComboBox users don't get this automatic behavior.Test plan
Closes #9820