@@ -68,12 +68,12 @@ export const ComboBox = ({
6868 dataFetcher,
6969 searchDebounceMs = 300 ,
7070} : SelectProps ) => {
71- const valueArray = arrayify ( value ) ;
71+ const valueArray = arrayify ( value ) as SelectOption [ ] ;
7272 const inputRef = useAutoFocus < HTMLInputElement > ( autoFocus ) ;
7373 const selectBoxRef = useRef < HTMLDivElement > ( null ) ;
7474
7575 // Lazy loading state
76- const [ dynamicOptions , setDynamicOptions ] = useState < SelectOption [ ] > ( [ ] ) ;
76+ const [ dynamicOptions , setDynamicOptions ] = useState < SelectOption [ ] > ( valueArray ) ;
7777 const [ isLoadingMore , setIsLoadingMore ] = useState ( false ) ;
7878 const [ searchTerm , setSearchTerm ] = useState ( '' ) ;
7979 const [ searchTimeout , setSearchTimeout ] = useState < NodeJS . Timeout | null > ( null ) ;
@@ -86,17 +86,20 @@ export const ComboBox = ({
8686 // Use ref to track if we're already loading data to prevent duplicate fetches
8787 const fetchedPagesRef = useRef ( new Set < number > ( ) ) ;
8888
89+ // Store the selected ids in a set for easy lookup - this is our source of truth for selection
90+ const selectedIds = useMemo ( ( ) => new Set ( valueArray . map ( ( item ) => item . value ) ) , [ valueArray ] ) ;
91+
92+ const sortOptionsBySelectedFirst = useCallback ( ( opt1 : SelectOption , opt2 : SelectOption ) => {
93+ return ( selectedIds . has ( opt2 . value ) ? 1 : 0 ) - ( selectedIds . has ( opt1 . value ) ? 1 : 0 )
94+ } , [ selectedIds ] ) ;
95+
8996 // Calculate items once - use dynamic options if dataFetcher is provided, otherwise use static options
9097 const options = useMemo ( ( ) => {
9198 return dataFetcher ? dynamicOptions : staticOptions || [ ] ;
9299 } , [ dataFetcher , dynamicOptions , staticOptions ] ) ;
93100
94- // Store the selected ids in a set for easy lookup - this is our source of truth for selection
95- const selectedIds = useMemo ( ( ) => new Set ( valueArray . map ( ( item ) => item . value ) ) , [ valueArray ] ) ;
96-
97- const sortOptionsBySelectedFirst = ( opt1 : SelectOption , opt2 : SelectOption ) => {
98- return ( selectedIds . has ( opt2 . value ) ? 1 : 0 ) - ( selectedIds . has ( opt1 . value ) ? 1 : 0 )
99- }
101+ // Duplicates can occur for a variety of reasons – e.g., when using this for a string field filter
102+ const optionIds = useMemo ( ( ) => new Set ( options . map ( ( item ) => item . value ) ) , [ options ] ) ;
100103
101104 // Handle individual item deselection
102105 const handleItemDeselect = useCallback (
@@ -121,14 +124,15 @@ export const ComboBox = ({
121124 } = useCombobox ( {
122125 items : options ,
123126 id : fieldId ,
127+ selectedItem : null ,
124128 itemToString : ( item ) => item ?. label ?? '' ,
125129 isItemDisabled : ( ) => disabled ,
126130 onInputValueChange : ( { inputValue } ) => {
127131 onInputChange ?.( inputValue ) ;
128132
129133 if ( dataFetcher && inputValue !== undefined ) {
130134 fetchedPagesRef . current . clear ( ) ;
131- setDynamicOptions ( [ ] ) ;
135+ setDynamicOptions ( valueArray ) ;
132136 setCurrentPage ( 1 ) ;
133137 setHasReachedEnd ( false ) ;
134138 setSearchTerm ( inputValue ) ;
@@ -187,14 +191,14 @@ export const ComboBox = ({
187191 if ( lastSearchTermRef . current === search ) {
188192 // Search term hasn't changed, merge the options in.
189193 if ( result && result . length > 0 ) {
190- setDynamicOptions ( ( prev ) => [ ...prev , ...result ] ) ;
194+ setDynamicOptions ( ( prev ) => [ ...prev , ...result . filter ( o => ! optionIds . has ( o . value ) ) ] ) ;
191195 setCurrentPage ( page ) ;
192196 } else {
193197 setHasReachedEnd ( true ) ;
194198 }
195199 } else {
196200 // If the search term has changed, we need to reset the options
197- setDynamicOptions ( result ) ;
201+ setDynamicOptions ( [ ... valueArray , ... result . filter ( o => ! selectedIds . has ( o . value ) ) ] ) ;
198202 setCurrentPage ( 1 ) ;
199203 setHasReachedEnd ( false ) ;
200204 fetchedPagesRef . current . clear ( ) ;
@@ -207,7 +211,7 @@ export const ComboBox = ({
207211 setIsLoadingMore ( false ) ;
208212 }
209213 } ,
210- [ dataFetcher , isOpen ]
214+ [ dataFetcher , isOpen , selectedIds , optionIds ]
211215 ) ;
212216
213217 // Scroll the menu to the top when it's opened.
0 commit comments