diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedNative-test.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedNative-test.js index 23e6a2123dd..d331d283029 100644 --- a/packages/react-native/Libraries/Animated/__tests__/AnimatedNative-test.js +++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedNative-test.js @@ -1027,21 +1027,17 @@ describe('Native Animated', () => { }).start(); jest.runAllTimers(); - Animated.timing(opacity, { - toValue: 4, - duration: 500, - useNativeDriver: false, - }).start(); - try { - process.env.NODE_ENV = 'development'; - expect(jest.runAllTimers).toThrow( - 'Attempting to run JS driven animation on animated node that has ' + - 'been moved to "native" earlier by starting an animation with ' + - '`useNativeDriver: true`', - ); - } finally { - process.env.NODE_ENV = 'test'; - } + expect( + Animated.timing(opacity, { + toValue: 4, + duration: 500, + useNativeDriver: false, + }).start, + ).toThrow( + 'Attempting to run JS driven animation on animated node that has ' + + 'been moved to "native" earlier by starting an animation with ' + + '`useNativeDriver: true`', + ); }); it('fails for unsupported styles', () => { diff --git a/packages/react-native/Libraries/Animated/animations/Animation.js b/packages/react-native/Libraries/Animated/animations/Animation.js index 6a14a87fabd..ad956cd79d9 100644 --- a/packages/react-native/Libraries/Animated/animations/Animation.js +++ b/packages/react-native/Libraries/Animated/animations/Animation.js @@ -11,9 +11,11 @@ 'use strict'; import type {PlatformConfig} from '../AnimatedPlatformConfig'; +import type AnimatedNode from '../nodes/AnimatedNode'; import type AnimatedValue from '../nodes/AnimatedValue'; import NativeAnimatedHelper from '../NativeAnimatedHelper'; +import AnimatedProps from '../nodes/AnimatedProps'; export type EndResult = {finished: boolean, value?: number, ...}; export type EndCallback = (result: EndResult) => void; @@ -65,6 +67,21 @@ export default class Animation { onEnd && onEnd(result); } + __findAnimatedPropsNodes(node: AnimatedNode): Array { + const result = []; + + if (node instanceof AnimatedProps) { + result.push(node); + return result; + } + + for (const child of node.__getChildren()) { + result.push(...this.__findAnimatedPropsNodes(child)); + } + + return result; + } + __startNativeAnimation(animatedValue: AnimatedValue): void { const startNativeAnimationWaitId = `${startNativeAnimationNextId}:startAnimation`; startNativeAnimationNextId += 1; @@ -88,6 +105,12 @@ export default class Animation { const {value} = result; if (value != null) { animatedValue.__onAnimatedValueUpdateReceived(value); + + // Once the JS side node is synced with the updated values, trigger an + // update on the AnimatedProps nodes to call any registered callbacks. + this.__findAnimatedPropsNodes(animatedValue).forEach(node => + node.update(), + ); } }, ); diff --git a/packages/react-native/Libraries/Animated/animations/DecayAnimation.js b/packages/react-native/Libraries/Animated/animations/DecayAnimation.js index f0042c5881d..cc6a37bfaa3 100644 --- a/packages/react-native/Libraries/Animated/animations/DecayAnimation.js +++ b/packages/react-native/Libraries/Animated/animations/DecayAnimation.js @@ -85,6 +85,15 @@ export default class DecayAnimation extends Animation { this._onUpdate = onUpdate; this.__onEnd = onEnd; this._startTime = Date.now(); + + if (!this._useNativeDriver && animatedValue.__isNative === true) { + throw new Error( + 'Attempting to run JS driven animation on animated node ' + + 'that has been moved to "native" earlier by starting an ' + + 'animation with `useNativeDriver: true`', + ); + } + if (this._useNativeDriver) { this.__startNativeAnimation(animatedValue); } else { diff --git a/packages/react-native/Libraries/Animated/animations/SpringAnimation.js b/packages/react-native/Libraries/Animated/animations/SpringAnimation.js index 69101dab030..49855295b47 100644 --- a/packages/react-native/Libraries/Animated/animations/SpringAnimation.js +++ b/packages/react-native/Libraries/Animated/animations/SpringAnimation.js @@ -221,6 +221,14 @@ export default class SpringAnimation extends Animation { } const start = () => { + if (!this._useNativeDriver && animatedValue.__isNative === true) { + throw new Error( + 'Attempting to run JS driven animation on animated node ' + + 'that has been moved to "native" earlier by starting an ' + + 'animation with `useNativeDriver: true`', + ); + } + if (this._useNativeDriver) { this.__startNativeAnimation(animatedValue); } else { diff --git a/packages/react-native/Libraries/Animated/animations/TimingAnimation.js b/packages/react-native/Libraries/Animated/animations/TimingAnimation.js index 5c0c3ce3814..a32c7543c68 100644 --- a/packages/react-native/Libraries/Animated/animations/TimingAnimation.js +++ b/packages/react-native/Libraries/Animated/animations/TimingAnimation.js @@ -112,6 +112,14 @@ export default class TimingAnimation extends Animation { this.__onEnd = onEnd; const start = () => { + if (!this._useNativeDriver && animatedValue.__isNative === true) { + throw new Error( + 'Attempting to run JS driven animation on animated node ' + + 'that has been moved to "native" earlier by starting an ' + + 'animation with `useNativeDriver: true`', + ); + } + // Animations that sometimes have 0 duration and sometimes do not // still need to use the native driver when duration is 0 so as to // not cause intermixed JS and native animations. diff --git a/packages/react-native/Libraries/Animated/useAnimatedProps.js b/packages/react-native/Libraries/Animated/useAnimatedProps.js index 865bddd8ca3..bf3a6d140c9 100644 --- a/packages/react-native/Libraries/Animated/useAnimatedProps.js +++ b/packages/react-native/Libraries/Animated/useAnimatedProps.js @@ -66,7 +66,9 @@ export default function useAnimatedProps( // changes), but `setNativeView` already optimizes for that. node.setNativeView(instance); - // NOTE: This callback is only used by the JavaScript animation driver. + // NOTE: When using the JS animation driver, this callback is called on + // every animation frame. When using the native driver, this callback is + // called when the animation completes. onUpdateRef.current = () => { if ( process.env.NODE_ENV === 'test' || @@ -82,12 +84,6 @@ export default function useAnimatedProps( // $FlowIgnore[not-a-function] - Assume it's still a function. // $FlowFixMe[incompatible-use] instance.setNativeProps(node.__getAnimatedValue()); - } else { - throw new Error( - 'Attempting to run JS driven animation on animated node ' + - 'that has been moved to "native" earlier by starting an ' + - 'animation with `useNativeDriver: true`', - ); } };