Skip to content

fix: evaluate mount position from JS when animated reactions never fire - #2720

Open
icantcodefyi wants to merge 1 commit into
gorhom:masterfrom
icantcodefyi:fix/mount-position-js-fallback
Open

fix: evaluate mount position from JS when animated reactions never fire#2720
icantcodefyi wants to merge 1 commit into
gorhom:masterfrom
icantcodefyi:fix/mount-position-js-fallback

Conversation

@icantcodefyi

@icantcodefyi icantcodefyi commented Jul 22, 2026

Copy link
Copy Markdown

Motivation

Fixes #2721 (and the stale-closed #2690; an earlier report #2719 was auto-closed for not following the issue form): on Reanimated 4, every useAnimatedReaction attached to a sheet instance can fail to register when the instance mounts while the JS thread is busy. Derived values (detents, isLayoutCalculated) still compute, but the reaction that runs the mount position evaluation never fires — the sheet mounts parked off-screen at container height, onChange never fires, and provided animatedIndex/animatedPosition values are never written. Reproduced in production on mid-tier Android (details and Sentry field evidence in #2719).

Changes

1. Seed containerHeight at the point of measurement (BottomSheetHostingContainer): write containerHeight alongside rawContainerHeight in the same layoutState.modify, so initial layout state doesn't depend on the raw→container reaction firing. This incorporates #2689 — credit to @christian-apollo; happy to rebase if that lands first. verticalInset handling matches the reaction it replaces (modal subtracts insets, non-modal doesn't; modals pass shouldCalculateHeight={false} so in practice this path serves non-modal sheets).

2. JS-driven mount evaluation fallback (BottomSheet): a mount effect re-dispatches evaluatePosition(ANIMATION_SOURCE.MOUNT) via runOnUI every 100ms (max 20 attempts) until didAnimateOnMount flips, skipping while an animation is already RUNNING (so keyboard/mount animations are never interrupted). It runs the exact evaluation the reaction would have run — same guards, same branches — so semantics are unchanged. When reactions are healthy, the first check observes didAnimateOnMount === true and the effect retires without dispatching anything.

Both parts are needed: without (1), evaluatePosition/snapToIndex early-exit on "layout not ready" forever; without (2), the sheet still mounts invisible with perfectly correct layout state because nothing ever moves it.

Behavior when healthy

  • Reactions registered fine → first interval tick sees didAnimateOnMount true → cleanup, zero UI dispatches.
  • animateOnMount (mount animation running) → RUNNING guard skips until it completes, then didAnimateOnMount is true → no-op.
  • Sheets mounted closed (index={-1}) → evaluation resolves the closed position exactly as the reaction path would.

Testing

Shipped as a patch-package in production (Android-heavy language-learning app): the mount-invisible reports stopped; an independent measureInWindow-based watchdog we run alongside confirms sheets now position on mount even on the devices that previously reproduced it.

On Reanimated v4, every useAnimatedReaction attached to a sheet instance
can fail to fire when the instance mounts while the JS thread is busy.
Derived values (detents, isLayoutCalculated) still compute correctly,
but the reaction that evaluates the initial position never runs and the
sheet stays parked off-screen at the container height, while onChange
and provided animatedIndex/animatedPosition values never update either.

Two changes:

1. Seed containerHeight alongside rawContainerHeight in the same
   layoutState.modify at the point of measurement, so initial layout
   does not depend on the raw->container reaction firing (mirrors gorhom#2689
   by @christian-apollo).

2. Re-dispatch the mount position evaluation from the JS side via
   runOnUI until didAnimateOnMount flips (100ms interval, max 20
   attempts), skipping while an animation is already running. This runs
   the exact evaluatePosition the reaction would have run; with healthy
   reactions the first check observes the mount already handled and
   does nothing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant