diff --git a/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js b/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js index f353d8c26df..57b9e696ad5 100644 --- a/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js +++ b/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js @@ -151,6 +151,66 @@ test('animation driven by onScroll event', () => { expect(viewElement.getBoundingClientRect().y).toBe(100); }); +test('animation driven by onScroll event when animated view is unmounted', () => { + const scrollViewRef = createRef(); + + component PressableWithNativeDriver(mountAnimatedView: boolean) { + const currScroll = useAnimatedValue(0); + + return ( + + {mountAnimatedView ? ( + + ) : null} + + + + + ); + } + + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render(); + }); + + Fantom.runTask(() => { + root.render(); + }); + + const scrollViewelement = ensureInstance( + scrollViewRef.current, + ReactNativeElement, + ); + + Fantom.scrollTo(scrollViewelement, { + x: 0, + y: 50, + }); + + Fantom.runWorkLoop(); +}); + test('animated opacity', () => { let _opacity; let _opacityAnimation; diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.cpp b/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.cpp index b37570ef276..ec4ffd7647d 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.cpp +++ b/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.cpp @@ -406,47 +406,51 @@ void NativeAnimatedNodesManager::handleAnimatedEvent( if (!isOnRenderThread_) { return; } + if (eventDrivers_.empty()) { + return; + } - if (!eventDrivers_.empty()) { - bool foundAtLeastOneDriver = false; + bool foundAtLeastOneDriver = false; - const auto key = EventAnimationDriverKey{ - .viewTag = viewTag, - .eventName = EventEmitter::normalizeEventType(eventName)}; - if (auto driversIter = eventDrivers_.find(key); - driversIter != eventDrivers_.end()) { - auto& drivers = driversIter->second; - if (!drivers.empty()) { - foundAtLeastOneDriver = true; - } - for (const auto& driver : drivers) { - if (auto value = driver->getValueFromPayload(eventPayload)) { - auto node = - getAnimatedNode(driver->getAnimatedNodeTag()); - stopAnimationsForNode(node->tag()); - if (node->setRawValue(value.value())) { - updatedNodeTags_.insert(node->tag()); - } + const auto key = EventAnimationDriverKey{ + .viewTag = viewTag, + .eventName = EventEmitter::normalizeEventType(eventName)}; + if (auto driversIter = eventDrivers_.find(key); + driversIter != eventDrivers_.end()) { + auto& drivers = driversIter->second; + if (!drivers.empty()) { + foundAtLeastOneDriver = true; + } + for (const auto& driver : drivers) { + if (auto value = driver->getValueFromPayload(eventPayload)) { + auto node = + getAnimatedNode(driver->getAnimatedNodeTag()); + if (node == nullptr) { + continue; + } + stopAnimationsForNode(node->tag()); + if (node->setRawValue(value.value())) { + updatedNodeTags_.insert(node->tag()); } } } + } - if (foundAtLeastOneDriver && !isEventAnimationInProgress_) { - // There is an animation driver handling this event and - // event driven animation has not been started yet. - isEventAnimationInProgress_ = true; - // Some platforms (e.g. iOS) have UI tick listener disable - // when there are no active animations. Calling - // `startRenderCallbackIfNeeded` will call platform specific code to - // register UI tick listener. - startRenderCallbackIfNeeded(); - // Calling startOnRenderCallback_ will register a UI tick listener. - // The UI ticker listener will not be called until the next frame. - // That's why, in case this is called from the UI thread, we need to - // proactivelly trigger the animation loop to avoid showing stale - // frames. - onRender(); - } + if (foundAtLeastOneDriver && !isEventAnimationInProgress_) { + // There is an animation driver handling this event and + // event driven animation has not been started yet. + isEventAnimationInProgress_ = true; + // Some platforms (e.g. iOS) have UI tick listener disable + // when there are no active animations. Calling + // `startRenderCallbackIfNeeded` will call platform specific code to + // register UI tick listener. + startRenderCallbackIfNeeded(); + // Calling startOnRenderCallback_ will register a UI tick listener. + // The UI ticker listener will not be called until the next frame. + // That's why, in case this is called from the UI thread, we need to + // proactivelly trigger the animation loop to avoid showing stale + // frames. + onRender(); } } diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/animated/event_drivers/EventAnimationDriver.h b/packages/react-native/ReactCxxPlatform/react/renderer/animated/event_drivers/EventAnimationDriver.h index 53362b9a90b..07a4633a100 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/animated/event_drivers/EventAnimationDriver.h +++ b/packages/react-native/ReactCxxPlatform/react/renderer/animated/event_drivers/EventAnimationDriver.h @@ -37,7 +37,7 @@ class EventAnimationDriver { protected: std::vector eventPath_; - Tag animatedValueTag_; + const Tag animatedValueTag_; }; struct EventAnimationDriverKey {