-
-
Notifications
You must be signed in to change notification settings - Fork 98
Description
Disclaimer
We're currently using a JS-level workaround (wrapping in an Animated.View with delayed opacity) rather than the proposed native fix. The investigation and fix were proposed by Claude Opus-4.5. I'm not familiar with the legend-list internals, so take with a pinch of salt but sharing in case it's helpful for community.
Description
When using initialScrollAtEnd={true} with waitForInitialLayout={true}, there's still a visible flash where the list appears at a nearly-correct scroll position, then jumps slightly as anchor corrections are applied.
Example
Screen.Recording.2025-12-11.at.16.03.36.mov
Root Cause Analysis
After tracing through the code, the issue appears to be in ensureInitialAnchor.ts. Currently, finishScrollTo is scheduled via requestAnimationFrame after every adjustment:
// Line 72-74 in ensureInitialAnchor.ts
requestAdjust(ctx, delta);
requestAnimationFrame(() => finishScrollTo(ctx));finishScrollTo sets didInitialScroll = true, which (combined with didContainersLayout) triggers readyToRender = true in setInitialRenderState. This controls the list's opacity via waitForInitialLayout.
The apparent problem: readyToRender becomes true immediately after the first adjustment is requested, but before it's visually applied. The user sees the list at the pre-correction position, then it jumps.
The anchor settling logic (tolerance check, max attempts, delta-not-improving) correctly tracks when corrections are done, but finishScrollTo is never called in those exit paths - only after requestAdjust.
Potential Fix (Untested)
Move finishScrollTo calls to when the anchor is actually done, rather than after each adjustment:
// When settled (delta <= tolerance for 2 ticks)
if (settledTicks >= INITIAL_ANCHOR_SETTLED_TICKS) {
state.initialAnchor = undefined;
+ finishScrollTo(ctx);
}
// When max attempts reached
if ((anchor.attempts ?? 0) >= INITIAL_ANCHOR_MAX_ATTEMPTS) {
state.initialAnchor = undefined;
+ finishScrollTo(ctx);
return;
}
// When delta not improving
if (lastDelta !== undefined && Math.abs(delta) >= Math.abs(lastDelta)) {
state.initialAnchor = undefined;
+ finishScrollTo(ctx);
return;
}
// Remove premature call after adjustment
requestAdjust(ctx, delta);
-
-requestAnimationFrame(() => finishScrollTo(ctx));Environment
- legend-list version: 3.0.0-beta.8
- Platform: Android simulator and device
- React Native: 0.76.x
Current Workaround
We're using a JS-level workaround - wrapping the list in an Animated.View with controlled opacity, using the onLoad callback with a small delay (~50ms) before showing:
const [isReady, setIsReady] = useState(false);
const handleLoad = useCallback(() => {
setTimeout(() => setIsReady(true), 50);
}, []);
<Animated.View style={{ opacity: isReady ? 1 : 0, flex: 1 }}>
<LegendList
onLoad={handleLoad}
initialScrollAtEnd={true}
waitForInitialLayout={true}
...
/>
</Animated.View>