react-native-reanimated
react-native-reanimated copied to clipboard
Layout "exiting" animation not working on first execution on iOS
Description
After upgrading to reanimated > rc-7, on iOS the first time we perform an exiting animation, it is not executed.
iOS: (the first fadeout is immediate) https://github.com/software-mansion/react-native-reanimated/assets/93535783/e3377e9e-3f3d-4504-8b4f-49899e67323a
Android: https://github.com/software-mansion/react-native-reanimated/assets/93535783/10082791-2d99-4501-89ab-6c6bc6c61505
Code:
const Component: FC = () => {
const [isLoading, setIsLoading] = useState(true);
return (
<View style={{ flex: 1, marginTop: 200 }}>
<Button
text='ciao'
onPress={() => {
setIsLoading(!isLoading);
}}
/>
{isLoading ? (
<AnimatedWrapper exiting={FadeOut.duration(2000)}>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
<Text>ciao</Text>
</AnimatedWrapper>
) : null}
</View>
);
};
Steps to reproduce
I did my best to create a reproducible but everything works fine in a demo project.
On the incriminated project, I see that on iOS the FadeOut worklet is not called when we press the button to hide the list for the first time.
Steps:
- Pressing the button "ciao" should FadeOut the list of "ciao"
- Pressing the button "ciao" a second time should bring the list back visible
- etc...
I understand that could be nice to have reproducible but unfortunately, I couldn't reproduce on a new project. I'm looking for clues to understand what kind of dependency is breaking things.
I tried upgrading the react-navigation libraries but without any success.
I tried to build the same component on the same app but on the StoryBook and the problem is not visible.
I appreciate any hint or any suggestion on further analysis that I can do.
Snack or a link to a repository
""
Reanimated version
3.1.0
React Native version
0.70.6
Platforms
Android, iOS
JavaScript runtime
Hermes
Workflow
React Native (without Expo)
Architecture
Paper (Old Architecture)
Build type
Debug mode
Device
iOS simulator
Device model
No response
Acknowledgements
Yes
Hey! 👋
It looks like you've omitted a few important sections from the issue template.
Please complete Snack or a link to a repository section.
Hey! 👋
The issue doesn't seem to contain a minimal reproduction.
Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?
@marcocaldera are you using react-native-safe-area-context? On 2.14.x I find that layout animations (I think exiting specifically) don't work in a component wrapped in SafeAreaContext, which in my case is pretty much everything 🙃
Hi @toadums thanks for at least giving me hope! I tried to remove react-native-safe-area-context from my project and using it on the demo one but the result is unfortunately still the same.
Working on the demo project, not working on the real one
Hi @marcocaldera, thanks for submitting the issue. How is it going with the bug? 🙈😄
I assume your app is private and you can't share any link to it, so it will be very difficult to find the problem, however:
Could you please provide a copy of your package.json?
Also did you try adding a delay before calling setIsLoading(!isLoading);? Does it change behaviour?
just upgraded from 2.14 to 3.3.0 - got the same issue, layout exiting animation doesn't work, the animated view disappears instantly on unmount. I don't use SafeAreaContext, but i use native stack navigator from react-navigation. If i replace all createNativeStackNavigator with createStackNavigator - the issue goes away, but it's not an acceptable workaround
const Screen = ({ route }) => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setIsLoading(false);
}, 2000);
}, []);
return (
<View>
<Button onPress={Navigation.goBack} title="back" style={{ marginTop: 100 }} />
{isLoading ? (
<Animated.View
exiting={FadeOutDown}
style={{
zIndex: 9999, // zIndex seems to be required here, otherwise the green view overlaps the red one instantly
backgroundColor: 'red',
height: '100%',
width: '100%',
}}
/>
) : (
<View
style={{
backgroundColor: 'green',
height: '100%',
width: '100%',
}}
/>
)}
</View>
);
};
I had a similar issue. However, I find that it relates to react-navigation. Especially when I set the headerBackground prop in the stack config, reanimated doesn't work at all.
return (
<Stack.Screen options={{
// removing this will make it work
headerBackground: () => <View style={{
flex:1,
width: '100%',
backgroundColor: 'green'
}} />
}}>
<Animated.View
layout={Layout.springify()}
style={{
paddingVertical: isLoading ? 40 : 20
}}>
{!isLoading &&
<Animated.View
entering={FadeIn}
layout={Layout.springify()}
/>
}
</Animated.View>
</Stack.Screen>
)
What happened is the transition happened immediately. It will work, however, if a fast refresh happened. And also, if I remove the headerBackground it works.
When I downgraded to 2.14, the transitions works.
Any updates on this issue?
I'm also having this issue (exiting animation not working in NativeStackNavigator) and unfortunately it's blocking me from updating to the lates react-native. I'm not good at debugging native code but let me know if I can help diagnose any issues.
Experiencing the same problem here. Entering works on first render, subsequent remounts of component does not show exit animation nor new enter animation when it returns.
I also have the same problem. Exiting animation doesn't work on iOS but it works on Android.
My issue may have actually been related to an issue with a custom exit animation. I was missing initial values.
Before:
class SlideOutLeft
extends ComplexAnimationBuilder
{
static createInstance() {
return new SlideOutLeft();
}
build = () => {
const [, config] = this.getAnimationAndConfig();
const callback = this.callbackV;
const initialValues = this.initialValues;
const { width } = Dimensions.get('window');
return () => {
'worklet';
return {
animations: {
transform: [
{
translateX: withTiming(-width, config),
},
],
},
initialValues: { ...initialValues },
callback,
};
};
};
}
after:
class SlideOutLeft
extends ComplexAnimationBuilder
{
static createInstance() {
return new SlideOutLeft();
}
build = () => {
const [, config] = this.getAnimationAndConfig();
const callback = this.callbackV;
const initialValues = this.initialValues;
const { width } = Dimensions.get('window');
return () => {
'worklet';
return {
animations: {
transform: [
{
translateX: withTiming(-width, config),
},
],
},
initialValues: { ...initialValues, transform: [{ translateX: 0 }] },
callback,
};
};
};
}
I also have the same problem.
a temp workaround that might not work for all cases
i'm on 3.5.1 btw
Edit: this does not help with the issue. I mentioned a different problem.
Old message
If it can be of any help, I had the exact same issue on an animation that was not a layout animation.
<Animated.View
style={[
{ flexDirection: isChecked ? "row" : "row-reverse" },
]}
/>
Also, I had the issue on one page but not on the other one 🤔 I simplified them both, going down to just my component all alone inside the page. Still worked on one side and not on the other side. They are different react-navigation pages in different stacks, that might be related. I'll try to keep digging.
Also, note that the workaround above with the if (!fix) return null did work 👍 .
Edit: this does not help with the issue. I mentioned a different problem.
Old message
To those who have the issue, I just noticed something. I'll try my best to explain.
I tried four pages with react-navigation.
A B C
| |
D E
On each of these pages, I put my switch component whose animation breaks after a while.
- I arrive on A, first time. ✅ animation works.
- I move to B, first time. ✅ animation works.
- I go back to A, animation is broken ❌ .
- I go to B again, second time. Animation still works. ✅
- I go back to A, animation is broken ❌ .
- I go to B again, third time. Animation still works. ✅
- I go to C, first time. Animation works ✅
- A still doesn't work ❌
- B does not work anymore ❌
- I go to C, then E. E works ✅ but C breaks ❌
So, it looks like every time I move to a new page with react navigation, it breaks the existing animations.
Anyone encounters this as well?
Note that I use @react-navigation/native (and not /stack)
Edit: this does not help with the issue. I mentioned a different problem.
Old message
Good news! I just made a minimal example that reproduces the issue :)
https://github.com/pierpo/repro-reanimated-ios-animation-bug
Two things to note:
- I am not talking about a layout animation, even though the symptoms are similar (so probably related)
- The color transition still works, even though the movement animation is broken.
https://github.com/software-mansion/react-native-reanimated/assets/6313316/9e82a13d-f17c-4865-b142-ed9f8dd3e00b
About everything I said above. It might be related (because the workaround is the same, and it's also only on iOS), but actually the symptoms are the opposite of the issue 🙈
In my example, it works the first time and not the next times. The issue mentions the opposite: it doesn't work the first time but works the subsequent times.
I'll try to reproduce the issue's exact problem and see if I can make a reproducible example for it as well.
@pierpo I've just tested your example, the bug appears only with Expo.
Oh! And the original post is not on Expo.
I should probably open another issue then. Even though there are similarities, it's probably a different problem. Can you put back the labels "missing repro"?
Thanks. Sorry about the confusion.
I am experiencing the same issue.
just upgraded from 2.14 to 3.3.0 - got the same issue, layout exiting animation doesn't work, the animated view disappears instantly on unmount. I don't use SafeAreaContext, but i use native stack navigator from react-navigation. If i replace all
createNativeStackNavigatorwithcreateStackNavigator- the issue goes away, but it's not an acceptable workaroundconst Screen = ({ route }) => { const [isLoading, setIsLoading] = useState(true); useEffect(() => { setTimeout(() => { setIsLoading(false); }, 2000); }, []); return ( <View> <Button onPress={Navigation.goBack} title="back" style={{ marginTop: 100 }} /> {isLoading ? ( <Animated.View exiting={FadeOutDown} style={{ zIndex: 9999, // zIndex seems to be required here, otherwise the green view overlaps the red one instantly backgroundColor: 'red', height: '100%', width: '100%', }} /> ) : ( <View style={{ backgroundColor: 'green', height: '100%', width: '100%', }} /> )} </View> ); };
Same issue man, using native stack useEffect + exiting does not work. Did you find any solution besides switching for stack navigator?
Any updates on this?
Same issue.
same issue
Adding z-index on style of Animated.View resolved issue for me.
<Animated.View
key={"uniqueKey"}
entering={FadeIn.duration(500)}
exiting={SlideOutLeft}
style={{
alignItems: "center",
justifyContent: "center",
backgroundColor: "white",
width: "100%",
height: "100%",
zIndex: 9999,
}}
>
Still the same issue, my custom exiting animation works on Android, not ios.
My entire app is wrapped in SafeAreaView import from react-native and createNativeStackNavigator
Adding Z index did not fix it for me either.
Also, the exiting animation only doesn't work on the initial run, but I need it to work on the initial render
Not working for me in iOS. My app is also wrapped in SafeAreaView and KeyboardAvoidingView.
Adding zIndex didn't fix it.
Same issue here.
Package.json for reference
{
"name": "xxxx",
"version": "1.0.0",
"main": "expo-router/entry",
"scripts": {
"clean": "expo prebuild --clean",
"start": "watchman watch-del . ; watchman watch-project . && APP_VARIANT=development expo start",
"preview:prod": "expo start --no-dev --minify",
"android": "cross-env STORYBOOK_ENABLED='false' APP_VARIANT=development expo run:android",
"ios": "cross-env STORYBOOK_ENABLED='false' APP_VARIANT=development expo run:ios",
"web": "expo start --web",
"storybook-generate": "sb-rn-get-stories",
"storybook-watch": "sb-rn-watcher",
"storybook": "sb-rn-get-stories && cross-env STORYBOOK_ENABLED='true' yarn start ",
"storybook:ios": "sb-rn-get-stories && cross-env STORYBOOK_ENABLED='true' expo run:ios",
"storybook:android": "sb-rn-get-stories && cross-env STORYBOOK_ENABLED='true' yarn start --android",
"postinstall": "patch-package"
},
"dependencies": {
"@date-fns/utc": "^1.2.0",
"@gorhom/bottom-sheet": "^4",
"@gorhom/portal": "^1.0.14",
"@react-native-firebase/app": "^18.7.3",
"@react-native-firebase/auth": "^18.7.3",
"@react-native-google-signin/google-signin": "10.1.1",
"@sentry/react-native": "5.19.1",
"@storybook/addon-actions": "^6.5.16",
"@storybook/addon-controls": "^6.5.16",
"@storybook/addon-ondevice-actions": "^7.6.10",
"@storybook/addon-ondevice-backgrounds": "^7.6.10",
"@storybook/addon-ondevice-controls": "^7.6.10",
"@storybook/addon-ondevice-notes": "^7.6.10",
"@storybook/react-native": "^7.6.10",
"@tanstack/react-query": "^5.15.0",
"@types/node": "^20.10.5",
"axios": "^1.6.3",
"base-64": "^1.0.0",
"buffer": "^6.0.3",
"cross-env": "^7.0.3",
"dotenv": "^16.3.1",
"expo": "~50.0.13",
"expo-apple-authentication": "~6.3.0",
"expo-application": "~5.8.3",
"expo-auth-session": "~5.4.0",
"expo-build-properties": "~0.11.1",
"expo-clipboard": "~5.0.1",
"expo-constants": "~15.4.5",
"expo-crypto": "~12.8.1",
"expo-custom-assets": "^1.3.0",
"expo-dev-client": "~3.3.10",
"expo-device": "~5.9.3",
"expo-font": "~11.10.3",
"expo-haptics": "~12.8.1",
"expo-linear-gradient": "~12.7.2",
"expo-linking": "~6.2.2",
"expo-router": "~3.4.8",
"expo-splash-screen": "~0.26.4",
"expo-status-bar": "~1.11.1",
"expo-system-ui": "~2.9.3",
"expo-web-browser": "~12.8.2",
"humanize-duration": "^3.31.0",
"lottie-react-native": "6.5.1",
"nativewind": "^2.0.11",
"patch-package": "^8.0.0",
"pkce-challenge": "^4.1.0",
"postcss": "8.4.23",
"postinstall-postinstall": "^2.1.0",
"react": "18.2.0",
"react-datocms": "^4.1.3",
"react-native": "0.73.4",
"react-native-gesture-handler": "~2.14.0",
"react-native-reanimated": "~3.6.2",
"react-native-reanimated-carousel": "^3.5.1",
"react-native-screens": "~3.29.0",
"react-native-svg": "15.1.0",
"react-native-swiper-flatlist": "^3.2.3",
"react-native-web": "~0.19.6",
"rive-react-native": "^6.2.3",
"sentry-expo": "~7.2.0",
"yarn": "^1.22.22"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/plugin-transform-export-namespace-from": "^7.23.4",
"@react-native-async-storage/async-storage": "1.21.0",
"@react-native-community/datetimepicker": "7.6.1",
"@react-native-community/slider": "4.4.2",
"@storybook/addon-backgrounds": "^7.6.6",
"@storybook/addon-essentials": "^7.6.6",
"@storybook/addon-interactions": "^7.6.6",
"@storybook/addon-links": "^7.6.6",
"@storybook/addon-onboarding": "^1.0.10",
"@storybook/blocks": "^7.6.6",
"@storybook/react": "^7.6.6",
"@storybook/react-webpack5": "^7.6.6",
"@storybook/test": "^7.6.6",
"@types/base-64": "^1.0.2",
"@types/humanize-duration": "^3.27.3",
"@types/react": "~18.2.14",
"babel-loader": "^8.3.0",
"date-fns": "^3.1.0",
"eslint": "^8.51.0",
"eslint-config-universe": "^12.0.0",
"eslint-plugin-storybook": "^0.6.15",
"react-dom": "18.2.0",
"react-native-safe-area-context": "4.8.2",
"storybook": "^7.6.6",
"tailwindcss": "3.3.2",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"private": true,
"resolutions": {
"wrap-ansi": "7.0.0",
"string-width": "4.1.0",
"react-refresh": "~0.14.0"
}
}
Adding z-index on style of Animated.View resolved issue for me.
<Animated.View key={"uniqueKey"} entering={FadeIn.duration(500)} exiting={SlideOutLeft} style={{ alignItems: "center", justifyContent: "center", backgroundColor: "white", width: "100%", height: "100%", zIndex: 9999, }} >
Confirmed just now it works.