You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
After upgrading from Expo SDK 54 (Reanimated 4.1.1) to Expo SDK 56 (Reanimated 4.4.1), useAnimatedStyle styles on a screen render the last-committed native view state instead of the current shared value after navigating away and back using a native stack.
The pattern: Screen A (with animated components) → navigate to Screen B → back to Screen A. On return, the native views show their pre-animation/initial state even though the shared values are correct on the UI thread. A subsequent re-render (any state change) restores the correct appearance.
Two concrete regressions:
Sliding tab indicator driven by translateX via withTiming renders at a stale position
Horizontal bar fill — progress shared value (0→1, withTiming) is at 1 on the UI thread but the native view renders the initial width: 0%
Both worked correctly on Reanimated 4.1.1 (same component code, same navigation pattern). This appears related to — and possibly a regression from — #8363 and #8307 which were marked fixed in 4.1.3.
Affected Patterns
1. Sliding indicator (translateX + width)
A useSharedValue pair (translateX, indicatorWidth) is updated via withTiming inside a useEffect whenever the active tab index changes. A useAnimatedStyle reads these values and applies transform: [{ translateX }] and width.
On return from Screen B:
Shared values are at the correct post-animation position (verified)
A useSharedValue(0) is driven from 0→1 via withDelay + withTiming inside a useEffect on mount. A useAnimatedStyle maps it to width: \${progress.value * 100}%``.
On return from Screen B:
progress.value === 1 on UI thread
Native Animated.View renders width: 0%
Steps to Reproduce
Create a screen (Screen A) with the two patterns above inside a react-native-screens native stack (expo-router SDK 56)
Wait for initial animations to complete
Navigate to a child screen (Screen B)
Navigate back (back gesture / hardware back button)
Observed: animated styles render initial/stale state on Screen A
Trigger any re-render → styles instantly snap to correct values
Expected Behaviour
On return to Screen A, useAnimatedStyle reflects the current shared values — same visual state as when the user left.
Actual Behaviour
Native views render their initial/pre-animation state despite shared values being correct. Resolves only on re-render.
Environment
Package
Version
react-native-reanimated
4.4.1
react-native-worklets
0.9.1
react-native-screens
4.25.2
expo-router
~56.2.8
react-native
0.85.3
react
19.2.3
Architecture
New Architecture (default in Expo SDK 56)
Platform
iOS + Android (both affected)
Additional Context
Worked: Expo SDK 54 — Reanimated 4.1.1, RN 0.81.5, same component code, same navigation pattern
Broken: Expo SDK 56 — Reanimated 4.4.1, RN 0.85.3
The desync appears tied to react-native-screens freezing Fabric views when a screen is covered by another screen. Reanimated completes withTiming on the UI thread but the commit never reaches the frozen native view. On unfreeze, the view restores its last-committed style rather than the current shared value.
Workaround attempted: re-assigning shared values directly on screen focus (runOnUI(() => { sharedValue.value = target; })()). Partially effective but causes a visible snap/flash which is unacceptable in production.
Description
After upgrading from Expo SDK 54 (Reanimated 4.1.1) to Expo SDK 56 (Reanimated 4.4.1),
useAnimatedStylestyles on a screen render the last-committed native view state instead of the current shared value after navigating away and back using a native stack.The pattern: Screen A (with animated components) → navigate to Screen B → back to Screen A. On return, the native views show their pre-animation/initial state even though the shared values are correct on the UI thread. A subsequent re-render (any state change) restores the correct appearance.
Two concrete regressions:
translateXviawithTimingrenders at a stale positionprogressshared value (0→1,withTiming) is at1on the UI thread but the native view renders the initialwidth: 0%Both worked correctly on Reanimated 4.1.1 (same component code, same navigation pattern). This appears related to — and possibly a regression from — #8363 and #8307 which were marked fixed in 4.1.3.
Affected Patterns
1. Sliding indicator (translateX + width)
A
useSharedValuepair (translateX,indicatorWidth) is updated viawithTiminginside auseEffectwhenever the active tab index changes. AuseAnimatedStylereads these values and appliestransform: [{ translateX }]andwidth.On return from Screen B:
Animated.Viewrenders attranslateX: 0/width: 0(initial state)2. Sequential fill animation (width: 0% → 100%)
A
useSharedValue(0)is driven from 0→1 viawithDelay + withTiminginside auseEffecton mount. AuseAnimatedStylemaps it towidth: \${progress.value * 100}%``.On return from Screen B:
progress.value === 1on UI threadAnimated.Viewrenderswidth: 0%Steps to Reproduce
react-native-screensnative stack (expo-router SDK 56)Expected Behaviour
On return to Screen A,
useAnimatedStylereflects the current shared values — same visual state as when the user left.Actual Behaviour
Native views render their initial/pre-animation state despite shared values being correct. Resolves only on re-render.
Environment
react-native-reanimated4.4.1react-native-worklets0.9.1react-native-screens4.25.2expo-router~56.2.8react-native0.85.3react19.2.3Additional Context
react-native-screensfreezing Fabric views when a screen is covered by another screen. Reanimated completeswithTimingon the UI thread but the commit never reaches the frozen native view. On unfreeze, the view restores its last-committed style rather than the current shared value.runOnUI(() => { sharedValue.value = target; })()). Partially effective but causes a visible snap/flash which is unacceptable in production.