Skip to content

Commit 8c61397

Browse files
authored
Prevent ComboBox item selection if menu is closing (#3999)
* Prevent key selection if user clicks on a option when the menu is transitioning out See #2181 * fix test
1 parent b5336b4 commit 8c61397

File tree

2 files changed

+29
-2
lines changed

2 files changed

+29
-2
lines changed

packages/@react-stately/combobox/src/useComboBoxState.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,12 +306,26 @@ export function useComboBoxState<T extends object>(props: ComboBoxStateOptions<T
306306
setFocusedState(isFocused);
307307
};
308308

309+
let selectionManagerProxy = useMemo(() => {
310+
let proxy = new Proxy(selectionManager, {
311+
get(target, prop, receiver) {
312+
// If the menu is closed, don't update the selected key.
313+
if (prop === 'replaceSelection' && !triggerState.isOpen) {
314+
return () => {};
315+
}
316+
return Reflect.get(target, prop, receiver);
317+
}
318+
});
319+
320+
return proxy;
321+
}, [selectionManager, triggerState.isOpen]);
322+
309323
return {
310324
...triggerState,
311325
toggle,
312326
open,
313327
close,
314-
selectionManager,
328+
selectionManager: selectionManagerProxy,
315329
selectedKey,
316330
setSelectedKey,
317331
disabledKeys,

packages/@react-stately/combobox/test/useComboBoxState.test.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ describe('useComboBoxState tests', function () {
177177
expect(result.current.selectionManager.selectedKeys).toContain('0');
178178
expect(result.current.selectionManager.selectedKeys).not.toContain('1');
179179

180+
act(() => {result.current.open();});
180181
act(() => result.current.selectionManager.replaceSelection('1'));
181182
expect(result.current.selectionManager.selectedKeys).toContain('0');
182183
expect(result.current.selectionManager.selectedKeys).not.toContain('1');
@@ -190,22 +191,34 @@ describe('useComboBoxState tests', function () {
190191
expect(result.current.selectionManager.selectedKeys).toContain('0');
191192
expect(result.current.selectionManager.selectedKeys).not.toContain('1');
192193

194+
act(() => {result.current.open();});
193195
act(() => result.current.selectionManager.replaceSelection('1'));
194196
expect(result.current.selectionManager.selectedKeys).toContain('1');
195197
expect(result.current.selectionManager.selectedKeys).not.toContain('0');
196198
expect(onSelectionChange).toHaveBeenCalledWith('1');
197199
});
198200

199-
it('supports sdefault no selection', function () {
201+
it('supports default no selection', function () {
200202
let initialProps = {...defaultProps};
201203
let {result} = renderHook((props) => useComboBoxState(props), {initialProps});
202204
expect(result.current.selectionManager.selectionMode).toBe('single');
203205
expect(result.current.selectionManager.selectedKeys.size).toBe(0);
204206

207+
act(() => {result.current.open();});
205208
act(() => result.current.selectionManager.replaceSelection('1'));
206209
expect(result.current.selectionManager.selectedKeys).toContain('1');
207210
expect(result.current.selectionManager.selectedKeys).not.toContain('0');
208211
expect(onSelectionChange).toHaveBeenCalledWith('1');
209212
});
213+
214+
it('won\'t perform replace a selection if the combobox is closed', function () {
215+
// This case covers if a option in the menu is clicked while the menu is closing
216+
let initialProps = {...defaultProps};
217+
let {result} = renderHook((props) => useComboBoxState(props), {initialProps});
218+
219+
act(() => result.current.selectionManager.replaceSelection('1'));
220+
expect(result.current.selectionManager.selectedKeys.size).toEqual(0);
221+
expect(onSelectionChange).toHaveBeenCalledTimes(0);
222+
});
210223
});
211224
});

0 commit comments

Comments
 (0)