-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Modal React Native break when using useAnimatedStyle in component #6659
Comments
This one is reported in react-native-screens and in Expo as well. I've done some research and created a reproduction here. A report was made in the RNScreens issue that this was working when using After investigating the commits between the two releases I was able to find the offending PR here: This one is containing an optimization to how shadow nodes are updated and if we revert this optimization (even tried reverting it from 3.16.1 by doing some manual copy/paste) the issues is no longer happening. bartlomiejbloniarz - would you be able to take a look? This is related to the new architecture only. |
@chrfalch Thanks for this investigation! I will take it from here. |
Awesome! Thanks :) |
@bartlomiejbloniarz hi, after upgrading to 0.76 I faced the same issue on Paper, for some reason the modal won't show up only on iOS though. Can't give a reproducer since the code around showing the modal is kinda complex: an element is highlighted with:
Tried with different dialog components just in case react-native-ui-lib one is problematic, no luck, used rn Modal, no luck. edit: When I display the dialog without the reanimated svg rect the dialog shows up just fine 🤷♂️ edit2: under some unknown circumstances, when hot reloading the Dialog shows up, feels like it's one commit behind. |
@efstathiosntonas Could you open a separate issue for that? In this one the |
@chrfalch I am still working on a solution. I have a (seemingly) working approach, but I'm still not 100% sure if it won't break anything. The issue is that the non-visible content is actually rendered outside of the screen. This happens, because the Reanimated uses Reverting the changes of #6214 doesn't actually solve the problem, as the old algorithm has the exact same flaw (but only in Release mode). |
Thanks!! Really appreciate you working on this @bartlomiejbloniarz - I didn't catch the release issue - so this was a good find. |
I am not sure that I understand the conversation here, I am having this issue with Android only. My app is on RN 0.76.2 and all dialogs/modals/bottom sheets are broken as they either do not show up or they get stuck all smushed in the top left corner and the whole screen becomes unusable. Is there a fix for this or something I can patch to make it work for now? |
The only way I managed to fix this at is to just not have |
@bartlomiejbloniarz Hey there, would you mind sharing your possible solution? I have been taking a stab at getting this to work with no good progress. Figured it'd be a good idea to ask from someone with partial success. |
same problem |
Any updates on this issue? |
@bartlomiejbloniarz Checked #6776, issue with modals looks to be fixed. |
@bartlomiejbloniarz I can confirm that #6776 fixed issue for me too. |
I am facing a similar issue after our repo upgraded the RN version to 0.76. The react navigation modals are not as before, for iPhone I see the cropped bottom as mentioned before, and for iPad a weird behavior, like if the content wrapper get bigger than the modal when you have some interaction with it. (Edit) We were able to "fix" the issue, previously we were using the navigation header that react navigation provides, after change for our custom header the issue is not happening anymore. Screen.Recording.2025-01-22.at.15.03.55.movSimulator.Screen.Recording.-.iPhone.15.-.2025-01-22.at.15.02.47.mp4 |
I am facing a similar issue when I am using useAnimatedStyle on the same page with a Modal. the Modal appears to be displayed above the screen view, with only the very bottom showing. When the Modal is made visible, this also freezes the App. As an aside, this very page works with Reanimated 3.6.1 and React Native 0.72. |
any update on this issue ? i am on latest version of react native and when i use useAnimatedStyle specially transforms modal content does not render i only see a overlay of modal const animatedStyle = useAnimatedStyle(() => ({
transform: [
{
translateX: boxX.value,
},
{
translateY: boxY.value,
},
{
rotate: `${boxRotation.value}rad`,
},
],
height: boxHeight.value,
width: boxWidth.value,
}));
//if i remove the transform part everything works well |
I was having the issue with Modal + useAnimatedStyle. I'm using:
PR #6776 fix it for me. @s1tony @Kaizodo you guys could test it and give feedback. Use
diff --git a/node_modules/react-native-reanimated/Common/cpp/reanimated/Fabric/ReanimatedCommitHook.cpp b/node_modules/react-native-reanimated/Common/cpp/reanimated/Fabric/ReanimatedCommitHook.cpp
index 4ad8463..70ccf81 100644
--- a/node_modules/react-native-reanimated/Common/cpp/reanimated/Fabric/ReanimatedCommitHook.cpp
+++ b/node_modules/react-native-reanimated/Common/cpp/reanimated/Fabric/ReanimatedCommitHook.cpp
@@ -78,7 +78,7 @@ RootShadowNode::Unshared ReanimatedCommitHook::shadowTreeWillCommit(
propsMap[&family].emplace_back(props);
});
- rootNode = cloneShadowTreeWithNewProps(*rootNode, propsMap);
+ rootNode = cloneShadowTreeWithNewPropsUnmounted(rootNode, propsMap);
// If the commit comes from React Native then pause commits from
// Reanimated since the ShadowTree to be committed by Reanimated may not
diff --git a/node_modules/react-native-reanimated/Common/cpp/reanimated/Fabric/ShadowTreeCloner.cpp b/node_modules/react-native-reanimated/Common/cpp/reanimated/Fabric/ShadowTreeCloner.cpp
index 5795b73..02637a6 100644
--- a/node_modules/react-native-reanimated/Common/cpp/reanimated/Fabric/ShadowTreeCloner.cpp
+++ b/node_modules/react-native-reanimated/Common/cpp/reanimated/Fabric/ShadowTreeCloner.cpp
@@ -1,5 +1,6 @@
#ifdef RCT_NEW_ARCH_ENABLED
+#include <react/renderer/core/DynamicPropsUtilities.h>
#include <reanimated/Fabric/ShadowTreeCloner.h>
#include <ranges>
@@ -7,6 +8,29 @@
namespace reanimated {
+ChildrenMap calculateChildrenMap(
+ const RootShadowNode &oldRootNode,
+ const PropsMap &propsMap) {
+ ChildrenMap childrenMap;
+
+ for (auto &[family, _] : propsMap) {
+ const auto ancestors = family->getAncestors(oldRootNode);
+
+ for (const auto &[parentNode, index] :
+ std::ranges::reverse_view(ancestors)) {
+ const auto parentFamily = &parentNode.get().getFamily();
+ auto &affectedChildren = childrenMap[parentFamily];
+
+ if (affectedChildren.contains(index)) {
+ continue;
+ }
+
+ affectedChildren.insert(index);
+ }
+ }
+ return childrenMap;
+}
+
ShadowNode::Unshared cloneShadowTreeWithNewPropsRecursive(
const ShadowNode &shadowNode,
const ChildrenMap &childrenMap,
@@ -43,33 +67,92 @@ ShadowNode::Unshared cloneShadowTreeWithNewPropsRecursive(
return result;
}
-RootShadowNode::Unshared cloneShadowTreeWithNewProps(
- const RootShadowNode &oldRootNode,
+ShadowNode::Unshared cloneShadowTreeWithNewPropsUnmountedRecursive(
+ ShadowNode::Shared const &oldShadowNode,
+ const ChildrenMap &childrenMap,
const PropsMap &propsMap) {
- ChildrenMap childrenMap;
+ if (oldShadowNode->getHasBeenPromoted()) {
+ return cloneShadowTreeWithNewPropsRecursive(
+ *oldShadowNode, childrenMap, propsMap);
+ }
- for (auto &[family, _] : propsMap) {
- const auto ancestors = family->getAncestors(oldRootNode);
+ auto shadowNode = std::const_pointer_cast<ShadowNode>(oldShadowNode);
+ auto layoutableShadowNode =
+ std::dynamic_pointer_cast<LayoutableShadowNode>(shadowNode);
+ if (layoutableShadowNode) {
+ layoutableShadowNode->dirtyLayout();
+ }
- for (const auto &[parentNode, index] :
- std::ranges::reverse_view(ancestors)) {
- const auto parentFamily = &parentNode.get().getFamily();
- auto &affectedChildren = childrenMap[parentFamily];
+ const auto family = &shadowNode->getFamily();
+ const auto affectedChildrenIt = childrenMap.find(family);
+ const auto propsIt = propsMap.find(family);
+ auto children = shadowNode->getChildren();
- if (affectedChildren.contains(index)) {
- continue;
+ if (affectedChildrenIt != childrenMap.end()) {
+ for (const auto index : affectedChildrenIt->second) {
+ auto clone = cloneShadowTreeWithNewPropsUnmountedRecursive(
+ children[index], childrenMap, propsMap);
+ if (clone != children[index]) {
+ shadowNode->replaceChild(*children[index], clone, index);
}
+ }
+ }
- affectedChildren.insert(index);
+ Props::Shared newProps = nullptr;
+
+ if (propsIt != propsMap.end()) {
+ PropsParserContext propsParserContext{
+ shadowNode->getSurfaceId(), *shadowNode->getContextContainer()};
+ newProps = shadowNode->getProps();
+ for (const auto &props : propsIt->second) {
+ newProps = shadowNode->getComponentDescriptor().cloneProps(
+ propsParserContext, newProps, RawProps(props));
}
}
+ if (newProps) {
+ auto &props = shadowNode->getProps();
+ auto &mutableProps = const_cast<Props::Shared &>(props);
+
+#ifdef ANDROID
+ auto &newPropsRef = const_cast<Props &>(*newProps);
+ newPropsRef.rawProps = mergeDynamicProps(
+ mutableProps->rawProps,
+ newProps->rawProps,
+ NullValueStrategy::Override);
+#endif
+ mutableProps = newProps;
+ auto layoutableShadowNode =
+ static_pointer_cast<YogaLayoutableShadowNode>(shadowNode);
+ layoutableShadowNode->updateYogaProps();
+ }
+
+ return shadowNode;
+}
+
+RootShadowNode::Unshared cloneShadowTreeWithNewProps(
+ const RootShadowNode &oldRootNode,
+ const PropsMap &propsMap) {
+ auto childrenMap = calculateChildrenMap(oldRootNode, propsMap);
+
// This cast is safe, because this function returns a clone
// of the oldRootNode, which is an instance of RootShadowNode
return std::static_pointer_cast<RootShadowNode>(
cloneShadowTreeWithNewPropsRecursive(oldRootNode, childrenMap, propsMap));
}
+RootShadowNode::Unshared cloneShadowTreeWithNewPropsUnmounted(
+ RootShadowNode::Unshared const &oldRootNode,
+ const PropsMap &propsMap) {
+ auto childrenMap = calculateChildrenMap(*oldRootNode, propsMap);
+
+ // This cast is safe, because this function returns a clone
+ // of the oldRootNode, which is an instance of RootShadowNode
+ return std::static_pointer_cast<RootShadowNode>(
+ cloneShadowTreeWithNewPropsUnmountedRecursive(
+ oldRootNode, childrenMap, propsMap));
+}
+
} // namespace reanimated
#endif // RCT_NEW_ARCH_ENABLED
diff --git a/node_modules/react-native-reanimated/Common/cpp/reanimated/Fabric/ShadowTreeCloner.h b/node_modules/react-native-reanimated/Common/cpp/reanimated/Fabric/ShadowTreeCloner.h
index e3f9f6d..8240776 100644
--- a/node_modules/react-native-reanimated/Common/cpp/reanimated/Fabric/ShadowTreeCloner.h
+++ b/node_modules/react-native-reanimated/Common/cpp/reanimated/Fabric/ShadowTreeCloner.h
@@ -24,6 +24,10 @@ RootShadowNode::Unshared cloneShadowTreeWithNewProps(
const RootShadowNode &oldRootNode,
const PropsMap &propsMap);
+RootShadowNode::Unshared cloneShadowTreeWithNewPropsUnmounted(
+ RootShadowNode::Unshared const &oldRootShadowNode,
+ const PropsMap &propsMap);
+
} // namespace reanimated
#endif // RCT_NEW_ARCH_ENABLED
|
Initial testing looks promising - have a few more things to test, will keep you updated. Thank you @wfern
|
@wfern After the patch it only works but not properly , sometimes it shows content mostly first attempt but afterwards its stops working randomly. |
After further testing, I am experiencing a similar problem, but for me, the content usually does not display on the first attempt, but after several attempts. |
Same issue |
For me this problem still looks like biggest issue, which stops me from adopting new architecture in my applications. |
I am thinking of migrating to reanimated based modal library Based upon the discussion here. #6776 |
Any news ? |
+1 for react-native-magic-modal. This is the only viable workaround i found, and it works out great. |
My Investigation of Modal + Reanimated Issues in New ArchitectureContextI've been investigating the Modal + Reanimated issues reported in #6659 and wanted to share my findings, particularly around iOS modal behavior as @delphinebugner described here Key Findings:
![]()
Screen.Recording.2025-02-16.at.14.37.39.movIf you need tab navigation but don't specifically require
My main motivation behind this hack is that need the "justify between look" aka "buttons at the bottom" look that is impossible with formSheet. Important NoteThis is not a solution to the underlying issue, but rather a workaround for those who:
The core issue with |
I began refactoring my code to the point of producing a Proof of Concept. While the modals display, the Press events inside of them do not always seem to fire - or are lost somehow, as my event handlers are not always called. Is anybody else experiencing anything similar? |
That is likely due to the same root cause of this issue, which is that Reanimated and React Native state get out of sync. While #6776 attempts to fix this, it introduces other issues. You may have some luck by animating some other property of your modal (e.g. zIndex) to force a commit to the |
Current update: I'm back to working on this issue. In the current state #6776 can't be merged as it introduces new issues. I might have to figure out a different solution. |
@bartlomiejbloniarz have you made any progress? |
@shubhamdeol I opened an issue in React Native, as I think it would be best to solve it there |
Good morning everyone. +1 on this. I have a modal component like so: import { Modal, View } from "react-native"
// ...
<Modal visible={showModal}>
<View style={$fullScreenStyle}>
</View>
</Modal> And I trigger the <Stack.Screen
name="Watching"
component={Screens.WatchingScreen}
options={{
presentation: "transparentModal",
animation: "fade",
}}
/> On the component tree, I have this Modal component at the very top, so this should definitely not occur. This did not happen until I set |
I was also facing the same issue with Modals + Reanimated in New Architecture, so I just wrapped the modal with a simple React Native view and the modals worked. I did it like this
|
Thank you @rafaelwds. That worked for me too. This bug was causing my |
Thanks, this worked with my modals acting funny. Going through the comments, I can't seem to wrap my head around why such a simple solution works for this 🙂 |
Description
before Im upgrade to react native 0.76.1(new arch), Im using react native 0.75.4 and its work fine with old arch. I have trace error why my modal children is not rendered, because i have useAnimatedStyle in component where my modal placed.
When i disabled useAnimatedStyle, its not break modal
Steps to reproduce
Snack or a link to a repository
https://snack.expo.dev/@tamacroft_expo/2fd4f5
Reanimated version
3.16.1
React Native version
0.76.1
Platforms
Android
JavaScript runtime
Hermes
Workflow
React Native
Architecture
Fabric (New Architecture)
Build type
Debug app & production bundle
Device
Android emulator
Device model
No response
Acknowledgements
Yes
The text was updated successfully, but these errors were encountered: