@@ -2,7 +2,7 @@ import { focusElementFromRefWhenAvailable, getFlattenedListItemsById, menuItemSe
22import { multiWordObjectFilter , NOOP } from '@jetstream/shared/utils' ;
33import { ListItem , ListItemGroup , Maybe } from '@jetstream/types' ;
44import isNumber from 'lodash/isNumber' ;
5- import { createRef , forwardRef , useCallback , useEffect , useImperativeHandle , useRef , useState } from 'react' ;
5+ import { createRef , forwardRef , useCallback , useEffect , useImperativeHandle , useMemo , useRef , useState } from 'react' ;
66import { Combobox , ComboboxPropsRef , ComboboxSharedProps } from './Combobox' ;
77import { ComboboxListItem , ComboboxListItemProps } from './ComboboxListItem' ;
88import { ComboboxListItemGroup } from './ComboboxListItemGroup' ;
@@ -65,24 +65,13 @@ export const ComboboxWithGroupedItems = forwardRef<ComboboxWithGroupedItemsRef,
6565 const comboboxRef = useRef < ComboboxPropsRef > ( null ) ;
6666 const [ filterTextNonDebounced , setFilterText ] = useState < string > ( '' ) ;
6767 const filterText = useDebounce ( filterTextNonDebounced , 300 ) ;
68- const [ selectedItem , setSelectedItem ] = useState < Maybe < ListItem > > ( ( ) =>
69- selectedItemId ? groups . flatMap ( ( group ) => group . items ) . find ( ( item ) => item . id === selectedItemId ) : null ,
70- ) ;
7168 const [ visibleItems , setVisibleItems ] = useState ( groups ) ;
72- const [ selectedItemLabel , setSelectedItemLabel ] = useState < string | null > ( ( ) => {
73- if ( selectedItem ) {
74- const selectedItem = groups . flatMap ( ( group ) => group . items ) . find ( ( item ) => item . id === selectedItemId ) ;
75- return selectedItem ? selectedItemLabelFn ( selectedItem ) : null ;
76- }
77- return null ;
78- } ) ;
79- const [ selectedItemTitle , setSelectedItemTitle ] = useState < string | null > ( ( ) => {
80- if ( selectedItem ) {
81- const selectedItem = groups . flatMap ( ( group ) => group . items ) . find ( ( item ) => item . id === selectedItemId ) ;
82- return selectedItem ? selectedItemLabelFn ( selectedItem ) : null ;
83- }
84- return null ;
85- } ) ;
69+ // Derived during render rather than mirrored into state with effects — see ComboboxWithItems
70+ // for why the effect-synced version risked "Maximum update depth exceeded".
71+ const itemsById = useMemo ( ( ) => getFlattenedListItemsById ( groups . flatMap ( ( group ) => group . items ) ) , [ groups ] ) ;
72+ const selectedItem : Maybe < ListItem > = selectedItemId ? itemsById [ selectedItemId ] : null ;
73+ const selectedItemLabel = selectedItem ? selectedItemLabelFn ( selectedItem ) : null ;
74+ const selectedItemTitle = selectedItem ? selectedItemTitleFn ( selectedItem ) || '' : null ;
8675 const [ focusedIndex , setFocusedIndex ] = useState < number | null > ( null ) ;
8776 const refs = visibleItems . flatMap ( ( group ) => group . items ) . map ( ( value ) => createRef < HTMLLIElement > ( ) ) ;
8877
@@ -97,24 +86,6 @@ export const ComboboxWithGroupedItems = forwardRef<ComboboxWithGroupedItemsRef,
9786 [ ] ,
9887 ) ;
9988
100- useEffect ( ( ) => {
101- if ( selectedItemId ) {
102- setSelectedItem ( getFlattenedListItemsById ( groups . flatMap ( ( group ) => group . items ) ) [ selectedItemId ] ) ;
103- } else {
104- setSelectedItem ( null ) ;
105- }
106- } , [ groups , selectedItemId ] ) ;
107-
108- useEffect ( ( ) => {
109- if ( selectedItem ) {
110- setSelectedItemLabel ( selectedItemLabelFn ( selectedItem ) ) ;
111- setSelectedItemTitle ( selectedItemTitleFn ( selectedItem ) || '' ) ;
112- } else {
113- setSelectedItemLabel ( null ) ;
114- setSelectedItemTitle ( null ) ;
115- }
116- } , [ selectedItem , selectedItemLabelFn , selectedItemTitleFn ] ) ;
117-
11889 useEffect ( ( ) => {
11990 if ( ! filterText ) {
12091 setVisibleItems ( groups ) ;
0 commit comments