SIGN IN SIGN UP
facebook / react-native UNCLAIMED

A framework for building native applications using React

0 0 154 C++

Sync JS-side AnimatedValue before invoking native-driver completion callback (#57021)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/57021

`Animation.__startAnimationIfNative` invoked the user's `start({finished})`
callback BEFORE syncing the JS-side `AnimatedValue` with the post-animation
value reported by the native side. Any caller that read the `AnimatedValue`
from inside the callback, or from a React re-render that the callback
triggered, observed the **pre-animation** value, producing visual jumps.

Concrete impact: an `<Animated.View>` whose `transform: [{scaleX}]` is
driven by `value.interpolate({inputRange: [0, 1], outputRange: [0.5, 1]})`
would render at `scaleX = interp(0) = 0.5` after the animation finished,
instead of at `scaleX = interp(1) = 1`. The same pattern affects opacity,
color and any other style derived from an animated value read during a
re-render scheduled by the completion callback.

This change reorders the native completion callback so
`animatedValue.__onAnimatedValueUpdateReceived(value, offset)` runs BEFORE
`this.__notifyAnimationEnd(result)`. The user's callback (and any re-render
it schedules) now observes the post-animation JS value.

The reorder is gated behind a new JS-only feature flag,
`animatedShouldSyncValueBeforeStartCallback`, which defaults to `true`
(the fix is on by default). Set the flag to `false` to opt out and
restore the pre-fix ordering as a kill-switch.

A Fantom integration test in `Animated-itest.js` exercises the exact
scenario: starts a `useNativeDriver: true` `Animated.timing(0 -> 1)`,
captures both `_value._value` and `value.interpolate(...).__getValue()`
inside the `start({finished})` callback and asserts the value matches the
flag state (pre-animation when off, post-animation when on).

## Behavior change to consider

The reorder also changes the order in which JS-side observers of the
`AnimatedValue` are notified relative to the `start({finished})` callback.
This was confirmed empirically against the current and the proposed
ordering.

Before (flag off):
1. `start({finished})` callback fires
2. `AnimatedValue.addListener(...)` subscribers receive the post-animation value

After (flag on):
1. `AnimatedValue.addListener(...)` subscribers receive the post-animation value
2. `start({finished})` callback fires

For the vast majority of callers this is irrelevant or strictly better
(observers and callback now agree on the same value). The flag defaults
to `true` so the fix ships immediately; the flag itself stays as a
kill-switch in case real-world callers turn out to depend on the
previous ordering. Once adoption has been verified the flag can be
removed entirely.

Changelog:
[General][Fixed] - Sync JS-side `Animated.Value` with the post-animation value before invoking `Animated.timing(...).start({finished})` callbacks so reads from inside the callback (or from React re-renders it triggers) observe the post-animation value rather than the pre-animation value. Gated behind a new JS-only feature flag, `animatedShouldSyncValueBeforeStartCallback`, defaulting to `true` (set to `false` to opt out).

Reviewed By: javache, zeyap

Differential Revision: D106940382

fbshipit-source-id: c6f27956030d2a9c4016a30e2dff8d9424ad7b33
F
Fabrizio Cucci committed
ee6958a9f44234ab08bc06aad599cdc34fa5b368
Parent: a18f53d
Committed by meta-codesync[bot] <215208954+meta-codesync[bot]@users.noreply.github.com> on 6/1/2026, 4:30:01 PM