Skip to content

Commit 8fea848

Browse files
committed
fix: prevent unbounded scroll-event recursion while scrollable is locked
`handleOnScroll` issued `scrollTo(scrollableRef, 0, lockPosition, false)` on every scroll event while the scrollable was LOCKED, including when the scrollable was already resting at `lockPosition`. `scrollTo` still drives the underlying scroll view, which emits another scroll event, which re-enters `handleOnScroll`, which calls `scrollTo` again. Once the offset has settled there is nothing left to break the cycle, so it recurses until the JS stack overflows with `RangeError: Maximum call stack size exceeded`, with the stack alternating between Reanimated's `useAnimatedScrollHandler` and this handler. Skip the `scrollTo` when the scrollable is already at the lock position, within a small epsilon to tolerate the sub-pixel offsets reported during scrolling. `scrollableContentOffsetY.value` is still assigned unconditionally, so the shared value is unchanged. `handleOnEndDrag` and `handleOnMomentumEnd` contain the same unconditional `scrollTo`, but they fire once per gesture rather than on every scroll event, so they cannot feed themselves. Left untouched to keep this fix minimal.
1 parent 9bfbeb5 commit 8fea848

1 file changed

Lines changed: 18 additions & 2 deletions

File tree

src/hooks/useScrollEventsHandlersDefault.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ export type ScrollEventContextType = {
1313
shouldLockInitialPosition: boolean;
1414
};
1515

16+
/**
17+
* Tolerance, in points, for treating the scrollable as already resting at the
18+
* lock position. Sub-pixel offsets are reported during scrolling, so an exact
19+
* comparison would keep re-issuing `scrollTo` and never settle.
20+
*/
21+
const SCROLL_LOCK_EPSILON = 0.5;
22+
1623
export const useScrollEventsHandlersDefault: ScrollEventsHandlersHookType = (
1724
scrollableRef,
1825
scrollableContentOffsetY
@@ -55,8 +62,17 @@ export const useScrollEventsHandlersDefault: ScrollEventsHandlersHookType = (
5562
const lockPosition = context.shouldLockInitialPosition
5663
? (context.initialContentOffsetY ?? 0)
5764
: 0;
58-
// @ts-expect-error
59-
scrollTo(scrollableRef, 0, lockPosition, false);
65+
/**
66+
* `scrollTo` drives the scrollable, which emits another scroll event,
67+
* which re-enters this handler. Issuing it unconditionally, including
68+
* when we are already sitting at `lockPosition`, makes that feedback
69+
* loop unbounded and overflows the stack. Only correct the offset when
70+
* it actually needs correcting.
71+
*/
72+
if (Math.abs(y - lockPosition) > SCROLL_LOCK_EPSILON) {
73+
// @ts-expect-error
74+
scrollTo(scrollableRef, 0, lockPosition, false);
75+
}
6076
scrollableContentOffsetY.value = lockPosition;
6177
return;
6278
}

0 commit comments

Comments
 (0)