Layout transitions stop working when `detachInactiveScreens` is set to false
Description
When we set detachInactiveScreens to false on the navigator, then, the layout transition is removed when we leave the screen with an AnimatedComponent that has a layout transition attached. This happens because the layout transition config is removed in the LayoutAnimationsProxy when the startAnimationsRecursively method is called and it is never added back again as the _configureLayoutTransition is not called again on the JS side, because the prevProps and current props still have the same layout property.
The problem seems to be similar to issues we had with freeze before but in this case we get invalid mutations indicating that the node is removed, whilst it can be brought back again.
I tested this with the BottomTabs navigator but the same issue will likely happen with other navigators, such as Stack.
Code snippet
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { useState } from 'react';
import { Button, StyleSheet, Text, View } from 'react-native';
import Animated, { LinearTransition } from 'react-native-reanimated';
import { enableScreens } from 'react-native-screens';
const Tab = createBottomTabNavigator();
enableScreens(true);
function Screen1() {
const [top, setTop] = useState(0);
return (
<View style={styles.container}>
<Button
title="Move"
onPress={() => setTop((prev) => (prev === 0 ? 100 : 0))}
/>
<Animated.View style={[styles.box, { top }]} layout={LinearTransition}>
<Text>Hello</Text>
</Animated.View>
</View>
);
}
function Screen2() {
return (
<View style={styles.container}>
<Text>Empty screen</Text>
</View>
);
}
function Tabs() {
return (
<Tab.Navigator
// This option breaks layout transitions
detachInactiveScreens={false}>
<Tab.Screen component={Screen1} name="Screen 1" />
<Tab.Screen component={Screen2} name="Screen 2" />
</Tab.Navigator>
);
}
export default function BottomTabsNavigatorExample() {
return (
<View style={styles.container}>
<Tabs />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
box: {
width: 100,
height: 100,
backgroundColor: 'red',
},
});
Example recordings
The behavior is different on Android and on iOS. On iOS the layout transition stops working at all whilst on Android it triggers the animation when the screen is re-entered.
| iOS | Android* |
|---|---|
*Interestingly, I saw the Android's issue on the expo snack but not in the Reanimated's example app when I copied this example. Maybe this is related to older versions of dependencies.
Steps to reproduce
- Press the
Movebutton and observe that the layout transition works - Navigate to the second tab
- Navigate back to the first tab
- Press the
Movebutton again and observe that the layout transition no longer works - Remove the
detachInactiveScreensproperty or set it totrue(default value) and repeat the process to see that everything works withoutdetachInactiveScreensbeing set tofalse
Snack or a link to a repository
https://snack.expo.dev/@matipl01/layout-transitions-repro
Reanimated version
3.17.5, 4.0.0-beta.5 (tested on these two)
React Native version
0.79, 0.80-rc.4 (tested on these two)
Platforms
iOS, Android
JavaScript runtime
None
Workflow
None
Architecture
None
Build type
No response
Device
No response
Host machine
None
Device model
No response
Acknowledgements
Yes
One more thing here. I know that using layout transitions with animated styles is hacky and is not something that we officially recommend or support but behavior is also different on Android than on iOS. I just replaced the top value stored in state by the SharedValue and animated style and got the following result:
Code snippet
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Button, StyleSheet, Text, View } from 'react-native';
import Animated, {
LinearTransition,
useAnimatedStyle,
useSharedValue
} from 'react-native-reanimated';
import { enableScreens } from 'react-native-screens';
const Tab = createBottomTabNavigator();
enableScreens(true);
function Screen1() {
const top = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => ({
top: top.value
}));
return (
<View style={styles.container}>
<Button
title='Move'
onPress={() => {
top.value = top.value === 0 ? 100 : 0;
}}
/>
<Animated.View
style={[styles.box, animatedStyle]}
layout={LinearTransition}>
<Text>Hello</Text>
</Animated.View>
</View>
);
}
function Screen2() {
return (
<View style={styles.container}>
<Text>Empty screen</Text>
</View>
);
}
function Tabs() {
return (
<Tab.Navigator
// This option breaks layout transitions
detachInactiveScreens={false}>
<Tab.Screen component={Screen1} name='Screen 1' />
<Tab.Screen component={Screen2} name='Screen 2' />
</Tab.Navigator>
);
}
export default function BottomTabsNavigatorExample() {
return (
<View style={[styles.container, { marginBottom: 100 }]}>
<Tabs />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16
},
box: {
width: 100,
height: 100,
backgroundColor: 'red'
}
});
| Android | iOS |
|---|---|
Original issue I got under my library: https://github.com/MatiPl01/react-native-sortables/issues/308
I see that latest stable reanimated version does not yet support RN 0.79. Could it be the reason for all these random reanimated crash? (facing this kind of issue as well using rn0.79.3)
I see that latest stable reanimated version does not yet support RN 0.79.
Latest reanimated version (3.18.0) supports RN from 0.75 to 0.80, so 0.79 is supported as well.
Could it be the reason for all these random reanimated crash?
Which random reanimated crashes? Can you give an example?
(facing this kind of issue as well using rn0.79.3)
This issue is likely not related to specific reanimated version but to how layout transitions cleanup is handled. Behavior is incorrect when detachInactiveScreens is set to false and layout transitions are cleaned up when the screen is left without being re-added when the screen is re-entered.
That's reassuring for 3.18.0 supporting RN0.79! it's my misunderstanding, reading the release notes the only explicit mention of support to 0.79 was version 4.x beta; but then 3.18 mention supporting 0.80 so it's on me.
as for random crashes:
using entering/exiting crashes randomly, not always but often enough to be an issue. for example, I have a chat textinput, with buttons at the end that disappear once I start typing:
{value.trim().length > 0&&<Animated.View entering={entering} exiting={exiting} style={{ position: 'absolute', right: 0, bottom: 3,height:46 ,zIndex:10 }}><Button....>
and a similar component just below, that takes it place and position if value.trim().length ===0.
entering and exiting are:
const entering = ZoomIn.springify().damping(20).stiffness(500)
const exiting = ZoomOut.duration(30)
another big crash happens randomly while navigating, I am still trying to investigate what exactly is causing this. I know it only happens on new architecture.
I don't know if react-navigation is at fault (bottom tabs navigation?); or react-native-screens; or reanimated, I will get back once I manage to get clearer info. thank you!
it's my misunderstanding, reading the release notes the only explicit mention of support to 0.79 was version 4.x beta; but then 3.18 mention supporting 0.80 so it's on me.
Here is a compatibility table. Reanimated never skips versions and supports at least a few latest RN versions (usually 3-5 major)
using entering/exiting crashes randomly
Can you prepare a repro or paste a complete code snippet that I would be able to run on my side? I would like to test it on my end after the weekend.
another big crash happens randomly while navigating
I am looking forward for information if you manage to find the culprit or prepare a reproduction example.
Thanks for cooperation!
it's my misunderstanding, reading the release notes the only explicit mention of support to 0.79 was version 4.x beta; but then 3.18 mention supporting 0.80 so it's on me.
Here is a compatibility table. Reanimated never skips versions and supports at least a few latest RN versions (usually 3-5 major)
using entering/exiting crashes randomly
Can you prepare a repro or paste a complete code snippet that I would be able to run on my side? I would like to test it on my end after the weekend.
another big crash happens randomly while navigating
I am looking forward for information if you manage to find the culprit or prepare a reproduction example.
Thanks for cooperation!
Ok I added crashlytics for my beta testers to catch more on the crash they are experiencing.
It happens completely at random, whatever they may be doing. here is the screenshot with more details:
and the other one:
I am not sure if they are the same as the one I sometimes experience since I upgraded to all latest version: "addViewAt: failed to insert view index=8 count=5" / index out of bounds etc
although the later I am wondering if react-native-screen is not the one at fault; even if at the end it seems like they are all relying one way or another to reanimated (it feels like you guys are carrying the entirety of RN libraries sometimes)
it's hard to make a repo, since it's part of a HUGE app of mine, although I can send you an apk if that helps? Let me know
@pierroo The crashes you are getting are probably from #7493