Skip to content

Commit 6e169e6

Browse files
zombieJclaude
andauthored
fix: notFoundContent failed the search state (#1201)
* chore: init test * fix: add rawOpen to useOpen hook return value Return both ssrSafeOpen and mergedOpen to prevent incorrect clearing of search text when mergedOpen differs from rawOpen. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add comment explaining rawOpen usage Clarify why rawOpen is used instead of mergedOpen to prevent premature clearing of search input. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 818b07c commit 6e169e6

File tree

3 files changed

+37
-6
lines changed

3 files changed

+37
-6
lines changed

src/BaseSelect/index.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
362362
// Not trigger `open` when `notFoundContent` is empty
363363
const emptyListContent = !notFoundContent && emptyOptions;
364364

365-
const [mergedOpen, triggerOpen, lockOptions] = useOpen(
365+
const [rawOpen, mergedOpen, triggerOpen, lockOptions] = useOpen(
366366
defaultOpen || false,
367367
open,
368368
onPopupVisibleChange,
@@ -430,12 +430,15 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
430430
onSearch(searchText, { source: 'submit' });
431431
};
432432

433-
// Close will clean up single mode search text
433+
// Clean up search value when the dropdown is closed.
434+
// We use `rawOpen` here to avoid clearing the search input when the dropdown is
435+
// programmatically closed due to `notFoundContent={null}` and no matching options.
436+
// This allows the user to continue typing their search query.
434437
React.useEffect(() => {
435-
if (!mergedOpen && !multiple && mode !== 'combobox') {
438+
if (!rawOpen && !multiple && mode !== 'combobox') {
436439
onInternalSearch('', false, false);
437440
}
438-
}, [mergedOpen]);
441+
}, [rawOpen]);
439442

440443
// ============================ Disabled ============================
441444
// Close dropdown & remove focus state when disabled change

src/hooks/useOpen.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export default function useOpen(
4343
propOpen: boolean,
4444
onOpen: (nextOpen: boolean) => void,
4545
postOpen: (nextOpen: boolean) => boolean,
46-
): [open: boolean, toggleOpen: TriggerOpenType, lockOptions: boolean] {
46+
): [rawOpen: boolean, open: boolean, toggleOpen: TriggerOpenType, lockOptions: boolean] {
4747
// SSR not support Portal which means we need delay `open` for the first time render
4848
const [rendered, setRendered] = useState(false);
4949

@@ -101,5 +101,5 @@ export default function useOpen(
101101
}
102102
});
103103

104-
return [mergedOpen, toggleOpen, lock];
104+
return [ssrSafeOpen, mergedOpen, toggleOpen, lock];
105105
}

tests/Select.test.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1997,6 +1997,34 @@ describe('Select.Basic', () => {
19971997
expect(container.querySelector('.rc-select-dropdown-empty')).toBeFalsy();
19981998
});
19991999

2000+
it('should allow typing when notFoundContent is null and no options match', () => {
2001+
const onSearch = jest.fn();
2002+
const { container } = render(
2003+
<Select showSearch notFoundContent={null} onSearch={onSearch}>
2004+
<Option value="jack">Jack</Option>
2005+
<Option value="lucy">Lucy</Option>
2006+
</Select>,
2007+
);
2008+
2009+
const input = container.querySelector('input');
2010+
2011+
// Type 'j' - should match 'Jack'
2012+
fireEvent.change(input, { target: { value: 'j' } });
2013+
expect(onSearch).toHaveBeenLastCalledWith('j');
2014+
expect(input.value).toBe('j');
2015+
expect(container.querySelectorAll('.rc-select-item-option')).toHaveLength(1);
2016+
2017+
// Type 'x' - no match, but input should still work
2018+
fireEvent.change(input, { target: { value: 'x' } });
2019+
expect(onSearch).toHaveBeenLastCalledWith('x');
2020+
expect(input.value).toBe('x');
2021+
2022+
// Type more characters - should continue working
2023+
fireEvent.change(input, { target: { value: 'xyz' } });
2024+
expect(onSearch).toHaveBeenLastCalledWith('xyz');
2025+
expect(input.value).toBe('xyz');
2026+
});
2027+
20002028
it('click outside to close select', () => {
20012029
jest.useFakeTimers();
20022030

0 commit comments

Comments
 (0)