react-native-reanimated icon indicating copy to clipboard operation
react-native-reanimated copied to clipboard

Layout transitions stop working when `detachInactiveScreens` is set to false

Open MatiPl01 opened this issue 6 months ago • 7 comments

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

  1. Press the Move button and observe that the layout transition works
  2. Navigate to the second tab
  3. Navigate back to the first tab
  4. Press the Move button again and observe that the layout transition no longer works
  5. Remove the detachInactiveScreens property or set it to true (default value) and repeat the process to see that everything works without detachInactiveScreens being set to false

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

MatiPl01 avatar Jun 05 '25 10:06 MatiPl01

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

MatiPl01 avatar Jun 05 '25 10:06 MatiPl01

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)

pierroo avatar Jun 14 '25 07:06 pierroo

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.

MatiPl01 avatar Jun 14 '25 10:06 MatiPl01

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!

pierroo avatar Jun 14 '25 10:06 pierroo

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!

MatiPl01 avatar Jun 14 '25 10:06 MatiPl01

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:

Image

and the other one:

Image

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 avatar Jun 15 '25 16:06 pierroo

@pierroo The crashes you are getting are probably from #7493

bartlomiejbloniarz avatar Jun 16 '25 09:06 bartlomiejbloniarz