From da913236ab2ec1b043a5a067aa0e958b8b294762 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Fri, 8 Mar 2024 06:48:58 -0800 Subject: [PATCH] add missing synchronisation call for native animated (#43374) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/43374 changelog: [internal] when animation that uses native driver finishes, it must synchronise the end state with shadow tree. `onUpdateRef` for native animated is only called when the animation finishes. Reviewed By: javache, yungsters Differential Revision: D54582987 fbshipit-source-id: 4320ed172b8bb4b22f82c6e24b47f88f1603e4fb --- .../Libraries/Animated/useAnimatedProps.js | 72 +++++++++++-------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/packages/react-native/Libraries/Animated/useAnimatedProps.js b/packages/react-native/Libraries/Animated/useAnimatedProps.js index 6be9fac9cd86cf..51196426ec2234 100644 --- a/packages/react-native/Libraries/Animated/useAnimatedProps.js +++ b/packages/react-native/Libraries/Animated/useAnimatedProps.js @@ -74,40 +74,56 @@ export default function useAnimatedProps( // every animation frame. When using the native driver, this callback is // called when the animation completes. onUpdateRef.current = () => { + if (node.__isNative || process.env.NODE_ENV === 'test') { + // Check 1: either tests are running or this is a native driven animation. + // In native driven animations, this callback is only called once the animation completes. + // Call `scheduleUpdate` to synchronise Fiber and Shadow tree. + return scheduleUpdate(); + } + if ( - process.env.NODE_ENV === 'test' || typeof instance !== 'object' || - typeof instance?.setNativeProps !== 'function' || - (isFabricInstance(instance) && !useNativePropsInFabric) + typeof instance?.setNativeProps !== 'function' ) { - // Schedule an update for this component to update `reducedProps`, - // but do not compute it immediately. If a parent also updated, we - // need to merge those new props in before updating. - scheduleUpdate(); - } else if (!node.__isNative) { + // Check 2: the instance does not support setNativeProps. Call `scheduleUpdate`. + return scheduleUpdate(); + } + + if (!isFabricInstance(instance)) { + // Check 3: this is a paper instance, call setNativeProps. // $FlowIgnore[not-a-function] - Assume it's still a function. // $FlowFixMe[incompatible-use] - instance.setNativeProps(node.__getAnimatedValue()); - if (isFabricInstance(instance)) { - // Keeping state of Fiber tree and Shadow tree in sync. - // - // This is done by calling `scheduleUpdate` which will trigger a commit. - // However, React commit is not fast enough to drive animations. - // This is where setNativeProps comes in handy but the state between - // Fiber tree and Shadow tree needs to be kept in sync. - // The goal is to call `scheduleUpdate` as little as possible to maintain - // performance but frequently enough to keep state in sync. - // Debounce is set to 48ms, which is 3 * the duration of a frame. - // 3 frames was the highest value where flickering state was not observed. - if (timerRef.current != null) { - clearTimeout(timerRef.current); - } - timerRef.current = setTimeout(() => { - timerRef.current = null; - scheduleUpdate(); - }, 48); - } + return instance.setNativeProps(node.__getAnimatedValue()); } + + if (!useNativePropsInFabric) { + // Check 4: setNativeProps are disabled. + return scheduleUpdate(); + } + + // This is a Fabric instance and setNativeProps are supported. + + // $FlowIgnore[not-a-function] - Assume it's still a function. + // $FlowFixMe[incompatible-use] + instance.setNativeProps(node.__getAnimatedValue()); + + // Keeping state of Fiber tree and Shadow tree in sync. + // + // This is done by calling `scheduleUpdate` which will trigger a commit. + // However, React commit is not fast enough to drive animations. + // This is where setNativeProps comes in handy but the state between + // Fiber tree and Shadow tree needs to be kept in sync. + // The goal is to call `scheduleUpdate` as little as possible to maintain + // performance but frequently enough to keep state in sync. + // Debounce is set to 48ms, which is 3 * the duration of a frame. + // 3 frames was the highest value where flickering state was not observed. + if (timerRef.current != null) { + clearTimeout(timerRef.current); + } + timerRef.current = setTimeout(() => { + timerRef.current = null; + scheduleUpdate(); + }, 48); }; const target = getEventTarget(instance);