@@ -189,9 +189,10 @@ function useLatestRef(val) {
189
189
* @param {Function } reducer Reducer function from downshift.
190
190
* @param {Object } props The hook props, also passed to createInitialState.
191
191
* @param {Function } createInitialState Function that returns the initial state.
192
+ * @param {Function } isStateEqual Function that checks if a previous state is equal to the next.
192
193
* @returns {Array } An array with the state and an action dispatcher.
193
194
*/
194
- function useEnhancedReducer ( reducer , props , createInitialState ) {
195
+ function useEnhancedReducer ( reducer , props , createInitialState , isStateEqual ) {
195
196
const prevStateRef = useRef ( )
196
197
const actionRef = useRef ( )
197
198
const enhancedReducer = useCallback (
@@ -219,7 +220,12 @@ function useEnhancedReducer(reducer, props, createInitialState) {
219
220
const action = actionRef . current
220
221
221
222
useEffect ( ( ) => {
222
- if ( action && prevStateRef . current && prevStateRef . current !== state ) {
223
+ const shouldCallOnChangeProps =
224
+ action &&
225
+ prevStateRef . current &&
226
+ ! isStateEqual ( prevStateRef . current , state )
227
+
228
+ if ( shouldCallOnChangeProps ) {
223
229
callOnChangeProps (
224
230
action ,
225
231
getState ( prevStateRef . current , action . props ) ,
@@ -228,7 +234,7 @@ function useEnhancedReducer(reducer, props, createInitialState) {
228
234
}
229
235
230
236
prevStateRef . current = state
231
- } , [ state , props , action ] )
237
+ } , [ state , action , isStateEqual ] )
232
238
233
239
return [ state , dispatchWithProps ]
234
240
}
@@ -240,13 +246,20 @@ function useEnhancedReducer(reducer, props, createInitialState) {
240
246
* @param {Function } reducer Reducer function from downshift.
241
247
* @param {Object } props The hook props, also passed to createInitialState.
242
248
* @param {Function } createInitialState Function that returns the initial state.
249
+ * @param {Function } isStateEqual Function that checks if a previous state is equal to the next.
243
250
* @returns {Array } An array with the state and an action dispatcher.
244
251
*/
245
- function useControlledReducer ( reducer , props , createInitialState ) {
252
+ function useControlledReducer (
253
+ reducer ,
254
+ props ,
255
+ createInitialState ,
256
+ isStateEqual ,
257
+ ) {
246
258
const [ state , dispatch ] = useEnhancedReducer (
247
259
reducer ,
248
260
props ,
249
261
createInitialState ,
262
+ isStateEqual ,
250
263
)
251
264
252
265
return [ getState ( state , props ) , dispatch ]
@@ -585,6 +598,23 @@ function getChangesOnSelection(props, highlightedIndex, inputValue = true) {
585
598
}
586
599
}
587
600
601
+ /**
602
+ * Check if a state is equal for dropdowns, by comparing isOpen, inputValue, highlightedIndex and selected item.
603
+ * Used by useSelect and useCombobox.
604
+ *
605
+ * @param {Object } prevState
606
+ * @param {Object } newState
607
+ * @returns {boolean } Wheather the states are deeply equal.
608
+ */
609
+ function isDropdownsStateEqual ( prevState , newState ) {
610
+ return (
611
+ prevState . isOpen === newState . isOpen &&
612
+ prevState . inputValue === newState . inputValue &&
613
+ prevState . highlightedIndex === newState . highlightedIndex &&
614
+ prevState . selectedItem === newState . selectedItem
615
+ )
616
+ }
617
+
588
618
// Shared between all exports.
589
619
const commonPropTypes = {
590
620
environment : PropTypes . shape ( {
@@ -646,6 +676,7 @@ export {
646
676
getItemAndIndex ,
647
677
useElementIds ,
648
678
getChangesOnSelection ,
679
+ isDropdownsStateEqual ,
649
680
commonDropdownPropTypes ,
650
681
commonPropTypes ,
651
682
}
0 commit comments