Skip to content

Commit

Permalink
fix: only update computed positions if it actually changed
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrieljablonski committed Feb 12, 2024
1 parent 6f700f4 commit 31358c9
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 37 deletions.
45 changes: 22 additions & 23 deletions src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { useTooltip } from 'components/TooltipProvider'
import useIsomorphicLayoutEffect from 'utils/use-isomorphic-layout-effect'
import { getScrollParent } from 'utils/get-scroll-parent'
import { computeTooltipPosition } from 'utils/compute-positions'
import type { IComputedPosition } from 'utils/compute-positions-types'
import { cssTimeToMs } from 'utils/css-time-to-ms'
import { deepEqual } from 'utils/deep-equal'
import coreStyles from './core-styles.module.css'
import styles from './styles.module.css'
import type {
Expand All @@ -15,7 +17,6 @@ import type {
GlobalCloseEvents,
IPosition,
ITooltip,
PlacesType,
TooltipImperativeOpenOptions,
} from './TooltipTypes'

Expand Down Expand Up @@ -70,9 +71,11 @@ const Tooltip = ({
const tooltipShowDelayTimerRef = useRef<NodeJS.Timeout | null>(null)
const tooltipHideDelayTimerRef = useRef<NodeJS.Timeout | null>(null)
const missedTransitionTimerRef = useRef<NodeJS.Timeout | null>(null)
const [actualPlacement, setActualPlacement] = useState(place)
const [inlineStyles, setInlineStyles] = useState({})
const [inlineArrowStyles, setInlineArrowStyles] = useState({})
const [computedPosition, setComputedPosition] = useState<IComputedPosition>({
tooltipStyles: {},
tooltipArrowStyles: {},
place,
})
const [show, setShow] = useState(false)
const [rendered, setRendered] = useState(false)
const [imperativeOptions, setImperativeOptions] = useState<TooltipImperativeOpenOptions | null>(
Expand Down Expand Up @@ -239,6 +242,14 @@ const Tooltip = ({
}
}, [show])

const handleComputedPosition = (newComputedPosition: IComputedPosition) => {
setComputedPosition((oldComputedPosition) =>
deepEqual(oldComputedPosition, newComputedPosition)
? oldComputedPosition
: newComputedPosition,
)
}

const handleShowTooltipDelayed = (delay = delayShow) => {
if (tooltipShowDelayTimerRef.current) {
clearTimeout(tooltipShowDelayTimerRef.current)
Expand Down Expand Up @@ -335,13 +346,7 @@ const Tooltip = ({
middlewares,
border,
}).then((computedStylesData) => {
if (Object.keys(computedStylesData.tooltipStyles).length) {
setInlineStyles(computedStylesData.tooltipStyles)
}
if (Object.keys(computedStylesData.tooltipArrowStyles).length) {
setInlineArrowStyles(computedStylesData.tooltipArrowStyles)
}
setActualPlacement(computedStylesData.place as PlacesType)
handleComputedPosition(computedStylesData)
})
}

Expand Down Expand Up @@ -439,13 +444,7 @@ const Tooltip = ({
// invalidate computed positions after remount
return
}
if (Object.keys(computedStylesData.tooltipStyles).length) {
setInlineStyles(computedStylesData.tooltipStyles)
}
if (Object.keys(computedStylesData.tooltipArrowStyles).length) {
setInlineArrowStyles(computedStylesData.tooltipArrowStyles)
}
setActualPlacement(computedStylesData.place as PlacesType)
handleComputedPosition(computedStylesData)
})
}, [
show,
Expand Down Expand Up @@ -819,7 +818,7 @@ const Tooltip = ({
}, [delayShow])

const actualContent = imperativeOptions?.content ?? content
const canShow = show && Object.keys(inlineStyles).length > 0
const canShow = show && Object.keys(computedPosition.tooltipStyles).length > 0

useImperativeHandle(forwardRef, () => ({
open: (options) => {
Expand Down Expand Up @@ -849,7 +848,7 @@ const Tooltip = ({
}
},
activeAnchor,
place: actualPlacement,
place: computedPosition.place,
isOpen: Boolean(rendered && !hidden && actualContent && canShow),
}))

Expand All @@ -863,7 +862,7 @@ const Tooltip = ({
styles['tooltip'],
styles[variant],
className,
`react-tooltip__place-${actualPlacement}`,
`react-tooltip__place-${computedPosition.place}`,
coreStyles[canShow ? 'show' : 'closing'],
canShow ? 'react-tooltip__show' : 'react-tooltip__closing',
positionStrategy === 'fixed' && coreStyles['fixed'],
Expand All @@ -882,7 +881,7 @@ const Tooltip = ({
}}
style={{
...externalStyles,
...inlineStyles,
...computedPosition.tooltipStyles,
opacity: opacity !== undefined && canShow ? opacity : undefined,
}}
ref={tooltipRef}
Expand All @@ -897,7 +896,7 @@ const Tooltip = ({
noArrow && coreStyles['noArrow'],
)}
style={{
...inlineArrowStyles,
...computedPosition.tooltipArrowStyles,
background: arrowColor
? `linear-gradient(to right bottom, transparent 50%, ${arrowColor} 50%)`
: undefined,
Expand Down
33 changes: 19 additions & 14 deletions src/utils/compute-positions-types.d.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import { CSSProperties } from 'react'
import type { Middleware } from '../components/Tooltip/TooltipTypes'
import type { Middleware, PlacesType } from '../components/Tooltip/TooltipTypes'

export interface IComputePositions {
elementReference?: Element | HTMLElement | null
tooltipReference?: Element | HTMLElement | null
tooltipArrowReference?: Element | HTMLElement | null
place?:
| 'top'
| 'top-start'
| 'top-end'
| 'right'
| 'right-start'
| 'right-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end'
place?: PlacesType
offset?: number
strategy?: 'absolute' | 'fixed'
middlewares?: Middleware[]
border?: CSSProperties['border']
}

export interface IComputedPosition {
tooltipStyles: {
left?: string
top?: string
border?: CSSProperties['border']
}
tooltipArrowStyles: {
left?: string
top?: string
right?: string
bottom?: string
borderRight?: CSSProperties['border']
borderBottom?: CSSProperties['border']
}
place: PlacesType
}
25 changes: 25 additions & 0 deletions src/utils/deep-equal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const isObject = (object: unknown): object is Record<string, unknown> => {
return object !== null && typeof object === 'object'
}

export const deepEqual = (object1: unknown, object2: unknown): boolean => {
if (!isObject(object1) || !isObject(object2)) {
return object1 === object2
}

const keys1 = Object.keys(object1)
const keys2 = Object.keys(object2)

if (keys1.length !== keys2.length) {
return false
}

return keys1.every((key) => {
const val1 = object1[key]
const val2 = object2[key]
if (isObject(val1) && isObject(val2)) {
return deepEqual(val1, val2)
}
return val1 === val2
})
}

0 comments on commit 31358c9

Please sign in to comment.