Skip to content

Commit 6fdee22

Browse files
committed
fix: timer active was lost - using useRef for timer reference
1 parent e385a97 commit 6fdee22

File tree

1 file changed

+52
-41
lines changed

1 file changed

+52
-41
lines changed

packages/webui/src/client/lib/VirtualElement.tsx

Lines changed: 52 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ interface IElementMeasurements {
1313
}
1414

1515
const IDLE_CALLBACK_TIMEOUT = 100
16-
// Increased delay for Safari, as Safari doesn't have scroll time when using 'smooth' scroll
17-
const SAFARI_VISIBILITY_DELAY = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) ? 100 : 0
1816

1917
/**
2018
* This is a component that allows optimizing the amount of elements present in the DOM through replacing them
@@ -71,15 +69,14 @@ export function VirtualElement({
7169
const [measurements, setMeasurements] = useState<IElementMeasurements | null>(null)
7270
const [ref, setRef] = useState<HTMLDivElement | null>(null)
7371

74-
let inViewChangetimer: NodeJS.Timeout | undefined
72+
// Timers for visibility changes:
73+
const inViewChangeTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
74+
const skipInitialrunRef = useRef<boolean>(true)
75+
const isTransitioning = useRef(false)
7576

7677
const showPlaceholder = !isShowingChildren && !initialShow
7778

7879
const isCurrentlyObserving = useRef(false)
79-
const isTransitioning = useRef(false)
80-
81-
// Track the last visibility change to debounce
82-
const lastVisibilityChangeRef = useRef<number>(0)
8380

8481
const styleObj = useMemo<React.CSSProperties>(
8582
() => ({
@@ -119,50 +116,64 @@ export function VirtualElement({
119116
}
120117
}, [ref, inView, placeholderHeight])
121118

122-
const onVisibleChanged = useCallback(
123-
(visible: boolean) => {
124-
const now = Date.now()
125-
126-
// Debounce visibility changes in Safari to prevent unnecessary recaconditions
127-
if (SAFARI_VISIBILITY_DELAY > 0 && now - lastVisibilityChangeRef.current < SAFARI_VISIBILITY_DELAY) {
128-
return
129-
}
119+
useEffect(() => {
120+
if (inView) {
121+
setIsShowingChildren(true)
122+
}
130123

131-
lastVisibilityChangeRef.current = now
132-
if (inView === visible) {
133-
return
134-
}
124+
// Startup skip:
125+
if (skipInitialrunRef.current) {
126+
skipInitialrunRef.current = false
127+
return
128+
}
135129

136-
setInView(visible)
137-
if (visible) {
138-
setIsShowingChildren(true)
139-
}
130+
if (isTransitioning.current) {
131+
return
132+
}
140133

141-
// Don't do updates while transitioning:
142-
if (isTransitioning.current) {
143-
return
144-
}
145-
isTransitioning.current = true
134+
isTransitioning.current = true
146135

147-
// Clear any existing timers
148-
if (inViewChangetimer) {
149-
clearTimeout(inViewChangetimer)
150-
}
136+
// Clear any existing timers
137+
if (inViewChangeTimerRef.current) {
138+
clearTimeout(inViewChangeTimerRef.current)
139+
inViewChangeTimerRef.current = undefined
140+
}
151141

152-
// Delay the observer connection to prevent flickering
153-
inViewChangetimer = setTimeout(() => {
154-
if (visible) {
142+
// Delay the visibility change to avoid flickering
143+
// But low enough for scrolling to be responsive
144+
inViewChangeTimerRef.current = setTimeout(() => {
145+
try {
146+
if (inView) {
155147
if (ref) {
156148
if (!isCurrentlyObserving.current) {
157149
resizeObserverManager.observe(ref, handleResize)
158150
isCurrentlyObserving.current = true
159151
}
160152
}
153+
} else {
154+
if (ref && isCurrentlyObserving.current) {
155+
resizeObserverManager.unobserve(ref)
156+
isCurrentlyObserving.current = false
157+
}
158+
setIsShowingChildren(false)
161159
}
160+
} catch (error) {
161+
console.error('Error in visibility change handler:', error)
162+
} finally {
162163
isTransitioning.current = false
163-
}, 100)
164+
inViewChangeTimerRef.current = undefined
165+
}
166+
}, 50)
167+
}, [inView, ref, handleResize, resizeObserverManager])
168+
169+
const onVisibleChanged = useCallback(
170+
(visible: boolean) => {
171+
// Only update state if there's a change
172+
if (inView !== visible) {
173+
setInView(visible)
174+
}
164175
},
165-
[ref]
176+
[inView]
166177
)
167178

168179
const isScrolling = (): boolean => {
@@ -193,11 +204,11 @@ export function VirtualElement({
193204
isCurrentlyObserving.current = false
194205
}
195206

196-
if (inViewChangetimer) {
197-
clearTimeout(inViewChangetimer)
207+
if (inViewChangeTimerRef.current) {
208+
clearTimeout(inViewChangeTimerRef.current)
198209
}
199210
}
200-
}, [ref, inView])
211+
}, [ref, inView, handleResize])
201212

202213
useEffect(() => {
203214
if (inView === true) {
@@ -263,7 +274,7 @@ export function VirtualElement({
263274
window.clearTimeout(optimizeTimeout)
264275
}
265276
}
266-
}, [ref, inView])
277+
}, [ref, inView, placeholderHeight])
267278

268279
return (
269280
<InView

0 commit comments

Comments
 (0)