Skip to content

Commit 7997f76

Browse files
authored
feat(hooks): remove mouse and touch events on unmount (#1581)
* feat(hooks): remove mouse and touch events on unmount * revert unrelated change
1 parent 32d675d commit 7997f76

File tree

4 files changed

+72
-74
lines changed

4 files changed

+72
-74
lines changed

src/hooks/__tests__/utils.test.js

+12-6
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,7 @@ describe('utils', () => {
8484
describe('useMouseAndTouchTracker', () => {
8585
test('renders without error', () => {
8686
expect(() => {
87-
renderHook(() =>
88-
useMouseAndTouchTracker(false, [], undefined, jest.fn()),
89-
)
87+
renderHook(() => useMouseAndTouchTracker(undefined, [], jest.fn()))
9088
}).not.toThrowError()
9189
})
9290

@@ -95,9 +93,11 @@ describe('utils', () => {
9593
addEventListener: jest.fn(),
9694
removeEventListener: jest.fn(),
9795
}
96+
const refs = []
97+
const handleBlur = jest.fn()
9898

99-
const {unmount, result} = renderHook(() =>
100-
useMouseAndTouchTracker(false, [], environment, jest.fn()),
99+
const {unmount, rerender, result} = renderHook(() =>
100+
useMouseAndTouchTracker(environment, refs, handleBlur),
101101
)
102102

103103
expect(environment.addEventListener).toHaveBeenCalledTimes(5)
@@ -123,6 +123,10 @@ describe('utils', () => {
123123
)
124124
expect(environment.removeEventListener).not.toHaveBeenCalled()
125125

126+
rerender()
127+
128+
expect(environment.removeEventListener).not.toHaveBeenCalled()
129+
126130
unmount()
127131

128132
expect(environment.addEventListener).toHaveBeenCalledTimes(5)
@@ -149,7 +153,9 @@ describe('utils', () => {
149153
)
150154

151155
expect(result.current).toEqual({
152-
current: {isMouseDown: false, isTouchMove: false, isTouchEnd: false},
156+
isMouseDown: false,
157+
isTouchMove: false,
158+
isTouchEnd: false,
153159
})
154160
})
155161
})

src/hooks/useCombobox/index.js

+21-18
Original file line numberDiff line numberDiff line change
@@ -116,17 +116,20 @@ function useCombobox(userProps = {}) {
116116
previousResultCountRef.current = items.length
117117
}
118118
})
119-
// Add mouse/touch events to document.
120-
const mouseAndTouchTrackersRef = useMouseAndTouchTracker(
121-
isOpen,
122-
[inputRef, menuRef, toggleButtonRef],
119+
const mouseAndTouchTrackers = useMouseAndTouchTracker(
123120
environment,
124-
() => {
125-
dispatch({
126-
type: stateChangeTypes.InputBlur,
127-
selectItem: false,
128-
})
129-
},
121+
[toggleButtonRef, menuRef, inputRef],
122+
useCallback(
123+
function handleBlur() {
124+
if (latest.current.state.isOpen) {
125+
dispatch({
126+
type: stateChangeTypes.InputBlur,
127+
selectItem: false,
128+
})
129+
}
130+
},
131+
[dispatch, latest],
132+
),
130133
)
131134
const setGetterPropCallInfo = useGetterPropsCalledChecker(
132135
'getInputProps',
@@ -309,7 +312,7 @@ function useCombobox(userProps = {}) {
309312

310313
const itemHandleMouseMove = () => {
311314
if (
312-
mouseAndTouchTrackersRef.current.isTouchEnd ||
315+
mouseAndTouchTrackers.isTouchEnd ||
313316
index === latestState.highlightedIndex
314317
) {
315318
return
@@ -353,7 +356,7 @@ function useCombobox(userProps = {}) {
353356
}
354357
},
355358

356-
[dispatch, elementIds, latest, mouseAndTouchTrackersRef, shouldScrollRef],
359+
[dispatch, elementIds, latest, mouseAndTouchTrackers, shouldScrollRef],
357360
)
358361

359362
const getToggleButtonProps = useCallback(
@@ -425,7 +428,7 @@ function useCombobox(userProps = {}) {
425428
if (
426429
environment?.document &&
427430
latestState.isOpen &&
428-
!mouseAndTouchTrackersRef.current.isMouseDown
431+
!mouseAndTouchTrackers.isMouseDown
429432
) {
430433
const isBlurByTabChange =
431434
event.relatedTarget === null &&
@@ -501,13 +504,13 @@ function useCombobox(userProps = {}) {
501504
}
502505
},
503506
[
504-
setGetterPropCallInfo,
505-
latest,
506-
elementIds,
507-
inputKeyDownHandlers,
508507
dispatch,
509-
mouseAndTouchTrackersRef,
508+
elementIds,
510509
environment,
510+
inputKeyDownHandlers,
511+
latest,
512+
mouseAndTouchTrackers,
513+
setGetterPropCallInfo,
511514
],
512515
)
513516

src/hooks/useSelect/index.js

+19-18
Original file line numberDiff line numberDiff line change
@@ -150,16 +150,20 @@ function useSelect(userProps = {}) {
150150
}
151151
// eslint-disable-next-line react-hooks/exhaustive-deps
152152
}, [])
153-
// Add mouse/touch events to document.
154-
const mouseAndTouchTrackersRef = useMouseAndTouchTracker(
155-
isOpen,
156-
[menuRef, toggleButtonRef],
153+
154+
const mouseAndTouchTrackers = useMouseAndTouchTracker(
157155
environment,
158-
() => {
159-
dispatch({
160-
type: stateChangeTypes.ToggleButtonBlur,
161-
})
162-
},
156+
[toggleButtonRef, menuRef],
157+
useCallback(
158+
function handleBlur() {
159+
if (latest.current.state.isOpen) {
160+
dispatch({
161+
type: stateChangeTypes.ToggleButtonBlur,
162+
})
163+
}
164+
},
165+
[dispatch, latest],
166+
),
163167
)
164168
const setGetterPropCallInfo = useGetterPropsCalledChecker(
165169
'getMenuProps',
@@ -365,10 +369,7 @@ function useSelect(userProps = {}) {
365369
})
366370
}
367371
const toggleButtonHandleBlur = () => {
368-
if (
369-
latestState.isOpen &&
370-
!mouseAndTouchTrackersRef.current.isMouseDown
371-
) {
372+
if (latestState.isOpen && !mouseAndTouchTrackers.isMouseDown) {
372373
dispatch({
373374
type: stateChangeTypes.ToggleButtonBlur,
374375
})
@@ -434,11 +435,11 @@ function useSelect(userProps = {}) {
434435
return toggleProps
435436
},
436437
[
437-
latest,
438+
dispatch,
438439
elementIds,
440+
latest,
441+
mouseAndTouchTrackers,
439442
setGetterPropCallInfo,
440-
dispatch,
441-
mouseAndTouchTrackersRef,
442443
toggleButtonKeyDownHandlers,
443444
],
444445
)
@@ -472,7 +473,7 @@ function useSelect(userProps = {}) {
472473

473474
const itemHandleMouseMove = () => {
474475
if (
475-
mouseAndTouchTrackersRef.current.isTouchEnd ||
476+
mouseAndTouchTrackers.isTouchEnd ||
476477
index === latestState.highlightedIndex
477478
) {
478479
return
@@ -525,7 +526,7 @@ function useSelect(userProps = {}) {
525526

526527
return itemProps
527528
},
528-
[latest, elementIds, mouseAndTouchTrackersRef, shouldScrollRef, dispatch],
529+
[latest, elementIds, mouseAndTouchTrackers, shouldScrollRef, dispatch],
529530
)
530531

531532
return {

src/hooks/utils.js

+20-32
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,7 @@ function stateReducer(s, a) {
7474
function getA11ySelectionMessage(selectionParameters) {
7575
const {selectedItem, itemToString} = selectionParameters
7676

77-
return selectedItem
78-
? `${itemToString(selectedItem)} has been selected.`
79-
: ''
77+
return selectedItem ? `${itemToString(selectedItem)} has been selected.` : ''
8078
}
8179

8280
/**
@@ -351,20 +349,17 @@ function getHighlightedIndexOnOpen(props, state, offset) {
351349
}
352350
return offset < 0 ? items.length - 1 : 0
353351
}
354-
355352
/**
356-
* Reuse the movement tracking of mouse and touch events.
353+
* Tracks mouse and touch events, such as mouseDown, touchMove and touchEnd.
357354
*
358-
* @param {boolean} isOpen Whether the dropdown is open or not.
359-
* @param {Array<Object>} downshiftElementRefs Downshift element refs to track movement (toggleButton, menu etc.)
360-
* @param {Object} environment Environment where component/hook exists.
361-
* @param {Function} handleBlur Handler on blur from mouse or touch.
362-
* @returns {Object} Ref containing whether mouseDown or touchMove event is happening
355+
* @param {Object} environment The environment to add the event listeners to, for instance window.
356+
* @param {Array<HTMLElement>} downshiftElementRefs The refs for the element that should not trigger a blur action from mouseDown or touchEnd.
357+
* @param {Function} handleBlur The function that is called if mouseDown or touchEnd occured outside the downshiftElements.
358+
* @returns {Object} The mouse and touch events information, if any of are happening.
363359
*/
364360
function useMouseAndTouchTracker(
365-
isOpen,
366-
downshiftElementRefs,
367361
environment,
362+
downshiftElementRefs,
368363
handleBlur,
369364
) {
370365
const mouseAndTouchTrackersRef = useRef({
@@ -375,45 +370,39 @@ function useMouseAndTouchTracker(
375370

376371
useEffect(() => {
377372
if (isReactNative || !environment) {
378-
return
373+
return noop
379374
}
380375

381-
// The same strategy for checking if a click occurred inside or outside downshift
382-
// as in downshift.js.
383-
const onMouseDown = () => {
376+
const downshiftElements = downshiftElementRefs.map(ref => ref.current)
377+
378+
function onMouseDown() {
384379
mouseAndTouchTrackersRef.current.isTouchEnd = false // reset this one.
385380
mouseAndTouchTrackersRef.current.isMouseDown = true
386381
}
387-
const onMouseUp = event => {
382+
function onMouseUp(event) {
388383
mouseAndTouchTrackersRef.current.isMouseDown = false
389384

390385
if (
391-
isOpen &&
392-
!targetWithinDownshift(
393-
event.target,
394-
downshiftElementRefs.map(ref => ref.current),
395-
environment,
396-
)
386+
!targetWithinDownshift(event.target, downshiftElements, environment)
397387
) {
398388
handleBlur()
399389
}
400390
}
401-
const onTouchStart = () => {
391+
function onTouchStart() {
402392
mouseAndTouchTrackersRef.current.isTouchEnd = false
403393
mouseAndTouchTrackersRef.current.isTouchMove = false
404394
}
405-
const onTouchMove = () => {
395+
function onTouchMove() {
406396
mouseAndTouchTrackersRef.current.isTouchMove = true
407397
}
408-
const onTouchEnd = event => {
398+
function onTouchEnd(event) {
409399
mouseAndTouchTrackersRef.current.isTouchEnd = true
410400

411401
if (
412-
isOpen &&
413402
!mouseAndTouchTrackersRef.current.isTouchMove &&
414403
!targetWithinDownshift(
415404
event.target,
416-
downshiftElementRefs.map(ref => ref.current),
405+
downshiftElements,
417406
environment,
418407
false,
419408
)
@@ -428,18 +417,17 @@ function useMouseAndTouchTracker(
428417
environment.addEventListener('touchmove', onTouchMove)
429418
environment.addEventListener('touchend', onTouchEnd)
430419

431-
// eslint-disable-next-line consistent-return
432420
return function cleanup() {
433421
environment.removeEventListener('mousedown', onMouseDown)
434422
environment.removeEventListener('mouseup', onMouseUp)
435423
environment.removeEventListener('touchstart', onTouchStart)
436424
environment.removeEventListener('touchmove', onTouchMove)
437425
environment.removeEventListener('touchend', onTouchEnd)
438426
}
439-
// eslint-disable-next-line react-hooks/exhaustive-deps
440-
}, [isOpen, environment])
427+
// eslint-disable-next-line react-hooks/exhaustive-deps -- refs don't change
428+
}, [environment, handleBlur])
441429

442-
return mouseAndTouchTrackersRef
430+
return mouseAndTouchTrackersRef.current
443431
}
444432

445433
/* istanbul ignore next */

0 commit comments

Comments
 (0)