Skip to content

Commit 3e34982

Browse files
fix: correctly clear timeout refs
1 parent 329f432 commit 3e34982

File tree

4 files changed

+45
-35
lines changed

4 files changed

+45
-35
lines changed

src/components/Tooltip/Tooltip.tsx

+17-34
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
getScrollParent,
99
computeTooltipPosition,
1010
cssTimeToMs,
11+
clearTimeoutRef,
1112
} from 'utils'
1213
import type { IComputedPosition } from 'utils'
1314
import { useTooltip } from 'components/TooltipProvider'
@@ -223,9 +224,7 @@ const Tooltip = ({
223224
if (show === wasShowing.current) {
224225
return
225226
}
226-
if (missedTransitionTimerRef.current) {
227-
clearTimeout(missedTransitionTimerRef.current)
228-
}
227+
clearTimeoutRef(missedTransitionTimerRef)
229228
wasShowing.current = show
230229
if (show) {
231230
afterShow?.()
@@ -257,9 +256,7 @@ const Tooltip = ({
257256
}
258257

259258
const handleShowTooltipDelayed = (delay = delayShow) => {
260-
if (tooltipShowDelayTimerRef.current) {
261-
clearTimeout(tooltipShowDelayTimerRef.current)
262-
}
259+
clearTimeoutRef(tooltipShowDelayTimerRef)
263260

264261
if (rendered) {
265262
// if the tooltip is already rendered, ignore delay
@@ -273,9 +270,7 @@ const Tooltip = ({
273270
}
274271

275272
const handleHideTooltipDelayed = (delay = delayHide) => {
276-
if (tooltipHideDelayTimerRef.current) {
277-
clearTimeout(tooltipHideDelayTimerRef.current)
278-
}
273+
clearTimeoutRef(tooltipHideDelayTimerRef)
279274

280275
tooltipHideDelayTimerRef.current = setTimeout(() => {
281276
if (hoveringTooltip.current) {
@@ -307,9 +302,7 @@ const Tooltip = ({
307302
setActiveAnchor(target)
308303
setProviderActiveAnchor({ current: target })
309304

310-
if (tooltipHideDelayTimerRef.current) {
311-
clearTimeout(tooltipHideDelayTimerRef.current)
312-
}
305+
clearTimeoutRef(tooltipHideDelayTimerRef)
313306
}
314307

315308
const handleHideTooltip = () => {
@@ -322,9 +315,7 @@ const Tooltip = ({
322315
handleShow(false)
323316
}
324317

325-
if (tooltipShowDelayTimerRef.current) {
326-
clearTimeout(tooltipShowDelayTimerRef.current)
327-
}
318+
clearTimeoutRef(tooltipShowDelayTimerRef)
328319
}
329320

330321
const handleTooltipPosition = ({ x, y }: IPosition) => {
@@ -386,9 +377,7 @@ const Tooltip = ({
386377
return
387378
}
388379
handleShow(false)
389-
if (tooltipShowDelayTimerRef.current) {
390-
clearTimeout(tooltipShowDelayTimerRef.current)
391-
}
380+
clearTimeoutRef(tooltipShowDelayTimerRef)
392381
}
393382

394383
// debounce handler to prevent call twice when
@@ -697,12 +686,8 @@ const Tooltip = ({
697686
setRendered(false)
698687
handleShow(false)
699688
setActiveAnchor(null)
700-
if (tooltipShowDelayTimerRef.current) {
701-
clearTimeout(tooltipShowDelayTimerRef.current)
702-
}
703-
if (tooltipHideDelayTimerRef.current) {
704-
clearTimeout(tooltipHideDelayTimerRef.current)
705-
}
689+
clearTimeoutRef(tooltipShowDelayTimerRef)
690+
clearTimeoutRef(tooltipHideDelayTimerRef)
706691
return true
707692
}
708693
return false
@@ -790,12 +775,8 @@ const Tooltip = ({
790775
handleShow(true)
791776
}
792777
return () => {
793-
if (tooltipShowDelayTimerRef.current) {
794-
clearTimeout(tooltipShowDelayTimerRef.current)
795-
}
796-
if (tooltipHideDelayTimerRef.current) {
797-
clearTimeout(tooltipHideDelayTimerRef.current)
798-
}
778+
clearTimeoutRef(tooltipShowDelayTimerRef)
779+
clearTimeoutRef(tooltipHideDelayTimerRef)
799780
}
800781
}, [])
801782

@@ -818,7 +799,11 @@ const Tooltip = ({
818799

819800
useEffect(() => {
820801
if (tooltipShowDelayTimerRef.current) {
821-
clearTimeout(tooltipShowDelayTimerRef.current)
802+
/**
803+
* if the delay changes while the tooltip is waiting to show,
804+
* reset the timer with the new delay
805+
*/
806+
clearTimeoutRef(tooltipShowDelayTimerRef)
822807
handleShowTooltipDelayed(delayShow)
823808
}
824809
}, [delayShow])
@@ -875,9 +860,7 @@ const Tooltip = ({
875860
clickable && coreStyles['clickable'],
876861
)}
877862
onTransitionEnd={(event: TransitionEvent) => {
878-
if (missedTransitionTimerRef.current) {
879-
clearTimeout(missedTransitionTimerRef.current)
880-
}
863+
clearTimeoutRef(missedTransitionTimerRef)
881864
if (show || event.propertyName !== 'opacity') {
882865
return
883866
}

src/test/utils.spec.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { debounce, deepEqual, computeTooltipPosition, cssTimeToMs } from 'utils'
1+
import { debounce, deepEqual, computeTooltipPosition, cssTimeToMs, clearTimeoutRef } from 'utils'
22

33
describe('compute positions', () => {
44
test('empty reference elements', async () => {
@@ -256,3 +256,19 @@ describe('deepEqual', () => {
256256
expect(deepEqual(obj1, obj2)).toBe(false)
257257
})
258258
})
259+
260+
describe('clearTimeoutRef', () => {
261+
jest.useFakeTimers()
262+
263+
const func = jest.fn()
264+
265+
test('clears timeout ref', () => {
266+
const timeoutRef = { current: setTimeout(func, 1000) }
267+
clearTimeoutRef(timeoutRef)
268+
269+
jest.runAllTimers()
270+
271+
expect(func).not.toHaveBeenCalled()
272+
expect(timeoutRef.current).toBe(null)
273+
})
274+
})

src/utils/clear-timeout-ref.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const clearTimeoutRef = (ref: React.MutableRefObject<NodeJS.Timeout | null>) => {
2+
if (ref.current) {
3+
clearTimeout(ref.current)
4+
// eslint-disable-next-line no-param-reassign
5+
ref.current = null
6+
}
7+
}
8+
9+
export default clearTimeoutRef

src/utils/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import debounce from './debounce'
66
import deepEqual from './deep-equal'
77
import getScrollParent from './get-scroll-parent'
88
import useIsomorphicLayoutEffect from './use-isomorphic-layout-effect'
9+
import clearTimeoutRef from './clear-timeout-ref'
910

1011
export type { IComputedPosition }
1112
export {
@@ -16,4 +17,5 @@ export {
1617
deepEqual,
1718
getScrollParent,
1819
useIsomorphicLayoutEffect,
20+
clearTimeoutRef,
1921
}

0 commit comments

Comments
 (0)