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

Layout "exiting" animation not working on first execution on iOS

Open marcocaldera opened this issue 2 years ago • 33 comments

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:

  1. Pressing the button "ciao" should FadeOut the list of "ciao"
  2. Pressing the button "ciao" a second time should bring the list back visible
  3. 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

marcocaldera avatar Jun 05 '23 15:06 marcocaldera

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.

github-actions[bot] avatar Jun 05 '23 15:06 github-actions[bot]

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?

github-actions[bot] avatar Jun 05 '23 15:06 github-actions[bot]

@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 🙃

toadums avatar Jun 05 '23 22:06 toadums

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

marcocaldera avatar Jun 06 '23 10:06 marcocaldera

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?

Latropos avatar Jun 13 '23 06:06 Latropos

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>
    );
};

lightrow avatar Jun 24 '23 11:06 lightrow

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.

idrakimuhamad avatar Jul 04 '23 03:07 idrakimuhamad

Any updates on this issue?

jvfalco1 avatar Jul 11 '23 13:07 jvfalco1

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.

tobobo avatar Jul 28 '23 16:07 tobobo

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.

sannajammeh avatar Aug 08 '23 00:08 sannajammeh

I also have the same problem. Exiting animation doesn't work on iOS but it works on Android.

skoob13 avatar Aug 22 '23 14:08 skoob13

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,
      };
    };
  };
}

tobobo avatar Aug 23 '23 17:08 tobobo

I also have the same problem.

MC-HaoPhan avatar Sep 13 '23 04:09 MC-HaoPhan

a temp workaround that might not work for all cases

image

i'm on 3.5.1 btw

lightrow avatar Sep 13 '23 18:09 lightrow

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 👍 .

pierpo avatar Oct 23 '23 15:10 pierpo

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)

pierpo avatar Oct 23 '23 16:10 pierpo

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

pierpo avatar Oct 24 '23 09:10 pierpo

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 avatar Oct 25 '23 10:10 pierpo

@pierpo I've just tested your example, the bug appears only with Expo.

Latropos avatar Oct 25 '23 16:10 Latropos

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.

pierpo avatar Oct 26 '23 08:10 pierpo

I am experiencing the same issue.

ibovegar avatar Nov 23 '23 06:11 ibovegar

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>
    );
};

Same issue man, using native stack useEffect + exiting does not work. Did you find any solution besides switching for stack navigator?

hadnet avatar Jan 15 '24 05:01 hadnet

Any updates on this?

animaonline avatar Feb 12 '24 20:02 animaonline

Same issue.

Rag0n avatar Feb 15 '24 14:02 Rag0n

same issue

angelo-hub avatar Feb 20 '24 18:02 angelo-hub

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,
        }}
 >

egingir avatar Feb 27 '24 19:02 egingir

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

johntheZ avatar Mar 26 '24 03:03 johntheZ

Not working for me in iOS. My app is also wrapped in SafeAreaView and KeyboardAvoidingView.

Adding zIndex didn't fix it.

edwinvrgs avatar Apr 09 '24 14:04 edwinvrgs

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"
    }
}

zane-commeatio avatar Apr 10 '24 17:04 zane-commeatio

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.

mikgross avatar Apr 21 '24 16:04 mikgross