Skip to content

Commit 31358c9

Browse files
fix: only update computed positions if it actually changed
1 parent 6f700f4 commit 31358c9

File tree

3 files changed

+66
-37
lines changed

3 files changed

+66
-37
lines changed

Diff for: src/components/Tooltip/Tooltip.tsx

+22-23
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import { useTooltip } from 'components/TooltipProvider'
66
import useIsomorphicLayoutEffect from 'utils/use-isomorphic-layout-effect'
77
import { getScrollParent } from 'utils/get-scroll-parent'
88
import { computeTooltipPosition } from 'utils/compute-positions'
9+
import type { IComputedPosition } from 'utils/compute-positions-types'
910
import { cssTimeToMs } from 'utils/css-time-to-ms'
11+
import { deepEqual } from 'utils/deep-equal'
1012
import coreStyles from './core-styles.module.css'
1113
import styles from './styles.module.css'
1214
import type {
@@ -15,7 +17,6 @@ import type {
1517
GlobalCloseEvents,
1618
IPosition,
1719
ITooltip,
18-
PlacesType,
1920
TooltipImperativeOpenOptions,
2021
} from './TooltipTypes'
2122

@@ -70,9 +71,11 @@ const Tooltip = ({
7071
const tooltipShowDelayTimerRef = useRef<NodeJS.Timeout | null>(null)
7172
const tooltipHideDelayTimerRef = useRef<NodeJS.Timeout | null>(null)
7273
const missedTransitionTimerRef = useRef<NodeJS.Timeout | null>(null)
73-
const [actualPlacement, setActualPlacement] = useState(place)
74-
const [inlineStyles, setInlineStyles] = useState({})
75-
const [inlineArrowStyles, setInlineArrowStyles] = useState({})
74+
const [computedPosition, setComputedPosition] = useState<IComputedPosition>({
75+
tooltipStyles: {},
76+
tooltipArrowStyles: {},
77+
place,
78+
})
7679
const [show, setShow] = useState(false)
7780
const [rendered, setRendered] = useState(false)
7881
const [imperativeOptions, setImperativeOptions] = useState<TooltipImperativeOpenOptions | null>(
@@ -239,6 +242,14 @@ const Tooltip = ({
239242
}
240243
}, [show])
241244

245+
const handleComputedPosition = (newComputedPosition: IComputedPosition) => {
246+
setComputedPosition((oldComputedPosition) =>
247+
deepEqual(oldComputedPosition, newComputedPosition)
248+
? oldComputedPosition
249+
: newComputedPosition,
250+
)
251+
}
252+
242253
const handleShowTooltipDelayed = (delay = delayShow) => {
243254
if (tooltipShowDelayTimerRef.current) {
244255
clearTimeout(tooltipShowDelayTimerRef.current)
@@ -335,13 +346,7 @@ const Tooltip = ({
335346
middlewares,
336347
border,
337348
}).then((computedStylesData) => {
338-
if (Object.keys(computedStylesData.tooltipStyles).length) {
339-
setInlineStyles(computedStylesData.tooltipStyles)
340-
}
341-
if (Object.keys(computedStylesData.tooltipArrowStyles).length) {
342-
setInlineArrowStyles(computedStylesData.tooltipArrowStyles)
343-
}
344-
setActualPlacement(computedStylesData.place as PlacesType)
349+
handleComputedPosition(computedStylesData)
345350
})
346351
}
347352

@@ -439,13 +444,7 @@ const Tooltip = ({
439444
// invalidate computed positions after remount
440445
return
441446
}
442-
if (Object.keys(computedStylesData.tooltipStyles).length) {
443-
setInlineStyles(computedStylesData.tooltipStyles)
444-
}
445-
if (Object.keys(computedStylesData.tooltipArrowStyles).length) {
446-
setInlineArrowStyles(computedStylesData.tooltipArrowStyles)
447-
}
448-
setActualPlacement(computedStylesData.place as PlacesType)
447+
handleComputedPosition(computedStylesData)
449448
})
450449
}, [
451450
show,
@@ -819,7 +818,7 @@ const Tooltip = ({
819818
}, [delayShow])
820819

821820
const actualContent = imperativeOptions?.content ?? content
822-
const canShow = show && Object.keys(inlineStyles).length > 0
821+
const canShow = show && Object.keys(computedPosition.tooltipStyles).length > 0
823822

824823
useImperativeHandle(forwardRef, () => ({
825824
open: (options) => {
@@ -849,7 +848,7 @@ const Tooltip = ({
849848
}
850849
},
851850
activeAnchor,
852-
place: actualPlacement,
851+
place: computedPosition.place,
853852
isOpen: Boolean(rendered && !hidden && actualContent && canShow),
854853
}))
855854

@@ -863,7 +862,7 @@ const Tooltip = ({
863862
styles['tooltip'],
864863
styles[variant],
865864
className,
866-
`react-tooltip__place-${actualPlacement}`,
865+
`react-tooltip__place-${computedPosition.place}`,
867866
coreStyles[canShow ? 'show' : 'closing'],
868867
canShow ? 'react-tooltip__show' : 'react-tooltip__closing',
869868
positionStrategy === 'fixed' && coreStyles['fixed'],
@@ -882,7 +881,7 @@ const Tooltip = ({
882881
}}
883882
style={{
884883
...externalStyles,
885-
...inlineStyles,
884+
...computedPosition.tooltipStyles,
886885
opacity: opacity !== undefined && canShow ? opacity : undefined,
887886
}}
888887
ref={tooltipRef}
@@ -897,7 +896,7 @@ const Tooltip = ({
897896
noArrow && coreStyles['noArrow'],
898897
)}
899898
style={{
900-
...inlineArrowStyles,
899+
...computedPosition.tooltipArrowStyles,
901900
background: arrowColor
902901
? `linear-gradient(to right bottom, transparent 50%, ${arrowColor} 50%)`
903902
: undefined,

Diff for: src/utils/compute-positions-types.d.ts

+19-14
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
import { CSSProperties } from 'react'
2-
import type { Middleware } from '../components/Tooltip/TooltipTypes'
2+
import type { Middleware, PlacesType } from '../components/Tooltip/TooltipTypes'
33

44
export interface IComputePositions {
55
elementReference?: Element | HTMLElement | null
66
tooltipReference?: Element | HTMLElement | null
77
tooltipArrowReference?: Element | HTMLElement | null
8-
place?:
9-
| 'top'
10-
| 'top-start'
11-
| 'top-end'
12-
| 'right'
13-
| 'right-start'
14-
| 'right-end'
15-
| 'bottom'
16-
| 'bottom-start'
17-
| 'bottom-end'
18-
| 'left'
19-
| 'left-start'
20-
| 'left-end'
8+
place?: PlacesType
219
offset?: number
2210
strategy?: 'absolute' | 'fixed'
2311
middlewares?: Middleware[]
2412
border?: CSSProperties['border']
2513
}
14+
15+
export interface IComputedPosition {
16+
tooltipStyles: {
17+
left?: string
18+
top?: string
19+
border?: CSSProperties['border']
20+
}
21+
tooltipArrowStyles: {
22+
left?: string
23+
top?: string
24+
right?: string
25+
bottom?: string
26+
borderRight?: CSSProperties['border']
27+
borderBottom?: CSSProperties['border']
28+
}
29+
place: PlacesType
30+
}

Diff for: src/utils/deep-equal.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const isObject = (object: unknown): object is Record<string, unknown> => {
2+
return object !== null && typeof object === 'object'
3+
}
4+
5+
export const deepEqual = (object1: unknown, object2: unknown): boolean => {
6+
if (!isObject(object1) || !isObject(object2)) {
7+
return object1 === object2
8+
}
9+
10+
const keys1 = Object.keys(object1)
11+
const keys2 = Object.keys(object2)
12+
13+
if (keys1.length !== keys2.length) {
14+
return false
15+
}
16+
17+
return keys1.every((key) => {
18+
const val1 = object1[key]
19+
const val2 = object2[key]
20+
if (isObject(val1) && isObject(val2)) {
21+
return deepEqual(val1, val2)
22+
}
23+
return val1 === val2
24+
})
25+
}

0 commit comments

Comments
 (0)