From 8d7a5833a21cf20f767fb6d674575ea00571f6fd Mon Sep 17 00:00:00 2001 From: dvd233 <111864431+dvd233@users.noreply.github.com> Date: Sat, 10 Oct 2026 18:30:39 +0800 Subject: [PATCH] fix: avoid duplicate search tags for grouped options --- src/Select.tsx | 14 ++++-- tests/Tags.test.tsx | 119 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 5 deletions(-) diff --git a/src/Select.tsx b/src/Select.tsx index 9735154f..a0689860 100644 --- a/src/Select.tsx +++ b/src/Select.tsx @@ -443,6 +443,10 @@ const Select = React.forwardRef { const hasItemMatchingSearch = (item: DefaultOptionType) => { + // Ignore when the search value equals an existing option value. + if (item?.[mergedFieldNames.value] === mergedSearchValue) { + return true; + } if (normalizedOptionFilterProp.length) { return normalizedOptionFilterProp.some((prop) => item?.[prop] === mergedSearchValue); } @@ -451,14 +455,14 @@ const Select = React.forwardRef hasItemMatchingSearch(item)) + filteredOptions.some( + (item) => + hasItemMatchingSearch(item) || + item[mergedFieldNames.options]?.some(hasItemMatchingSearch), + ) ) { return filteredOptions; } - // ignore when search value equal select input value - if (filteredOptions.some((item) => item[mergedFieldNames.value] === mergedSearchValue)) { - return filteredOptions; - } // Skip creating temp tag option if it matches a disabled option value if (valueOptions.get(mergedSearchValue)?.disabled) { return filteredOptions; diff --git a/tests/Tags.test.tsx b/tests/Tags.test.tsx index 5a4ad6e6..1ae8f966 100644 --- a/tests/Tags.test.tsx +++ b/tests/Tags.test.tsx @@ -621,6 +621,125 @@ describe('Select.Tags', () => { expect(container.querySelectorAll('.rc-select-item')).toHaveLength(5); }); + // https://github.com/react-component/select/issues/795 + it.each([ + { + name: 'options', + props: { options: [{ label: 'Group', options: [{ label: 'g1v1', value: 'g1v1' }] }] }, + value: 'g1v1', + }, + { + name: 'children', + props: { + children: ( + + + + ), + }, + value: 'g1v1', + }, + { + name: 'fieldNames', + props: { + fieldNames: { label: 'text', value: 'code', options: 'items' }, + options: [{ text: 'Group', items: [{ text: 'g1v1', code: 'g1v1' }] }], + }, + value: 'g1v1', + }, + { + name: 'label match', + props: { + showSearch: { optionFilterProp: 'label' }, + options: [{ label: 'Group', options: [{ label: 'g1v1', value: 'key' }] }], + }, + value: 'key', + }, + { + name: 'multiple filter fields', + props: { + showSearch: { optionFilterProp: ['label', 'description'] }, + options: [ + { label: 'Group', options: [{ label: 'Label', value: 'key', description: 'g1v1' }] }, + ], + }, + value: 'key', + }, + { + name: 'value match with a different filter field', + props: { + showSearch: { optionFilterProp: 'label' }, + options: [{ label: 'Group', options: [{ label: 'g1v1-label', value: 'g1v1' }] }], + }, + value: 'g1v1', + }, + ])('does not add a search tag matching grouped $name', ({ props, value }) => { + const onChange = jest.fn(); + const { container } = render(, + ); + + fireEvent.change(container.querySelector('input'), { target: { value: searchValue } }); + expect( + Array.from(container.querySelectorAll('.rc-select-item-option-content')).map( + (option) => option.textContent, + ), + ).toEqual(labels); + }, + ); + + it.each([false, true])('respects a filtered-out grouped value with disabled=%s', (disabled) => { + const onChange = jest.fn(); + const { container } = render( +