Search code examples
react-nativeanimationreact-navigationlistenerreact-animated

Animation won't start if state changed (short snack for demonstration)


Snack is here

Hello, I'm hard stuck on a silly problem and I'm becoming nut.

I just wanted to make a simple and elegant animation when a screen is focused (in a tab bar navigation). My snack works perfectly until I perform a state change in my screen. Then the animation just won't start, even though the callback from focus listener is called and executed (check logs)... WHY?

I made a button to trigger manually the animation... and it works!???? I think I made the snack clear, but if you need more information, please ask me. I beg you, please help a brother in despair.

Snack is here

If you're lazy to click the Snack:

import React, { useState, useEffect } from "react";
import { Text, View, Animated, Dimensions, StyleSheet, SafeAreaView, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

function HomeScreen({navigation}) {
  const initialXPos = Dimensions.get("window").height * 0.5 ;
  const xPos = new Animated.Value(initialXPos);
  const opacity = new Animated.Value(0);
  const [change, setChange] = useState(true)

   useEffect(() => {
    const unsubscribe = navigation.addListener("focus", comingFromBot);
    return unsubscribe;
  }, []);

  const comingFromBot = () => {
    xPos.setValue(initialXPos);
    opacity.setValue(0);
    Animated.parallel([
      Animated.spring(xPos, {
        toValue: 100,
        tension:3,
        useNativeDriver: true,
      }),
      Animated.timing(opacity, {
        toValue: 1,
        duration: 1000,
        useNativeDriver: true,
      }),
    ]).start();
    console.log("Animation's Fired!");
  };

  return (
    <SafeAreaView style={{flex:1}}>
      <Animated.View style={[
            styles.container,
            { transform: [{ translateY: xPos }] },
            { opacity: opacity },
          ]}>
        <Text style={{fontSize:30}}>{change ? "Home!" : "TIMMY!"}</Text>
      </Animated.View>

      {/* debug */}
      <View style={styles.fire}>
        <Button title="fire" onPress={() => comingFromBot()}/>
      </View>

      <View style={styles.change}>
        <Button title="change" onPress={() => setChange(!change)}/>
      </View>
    </SafeAreaView>
  );
}
const styles = StyleSheet.create({
  container: { flex: 1, alignItems: 'center' },
  fire:{position:"absolute", width:"100%", bottom:0},
  change:{position:"absolute", width:"100%", bottom:48}
  });

function SettingsScreen() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', padding:8 }}>
      <Text>{"Go to Home tab again, and notice the animation.\n\nEXCEPT if we changed the text... WHY?\n\nBUT still works if we fire the animation with the button, but after still won't work on focus detection... HOW?\n\nWorks if you hot reload / hard reload the app... HELP?"}</Text>
    </View>
  );
}

const Tab = createBottomTabNavigator();

function MyTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <MyTabs />
    </NavigationContainer>
  );
}


Solution

  • I finally ended with this, thanks to @satya164: Snack

    I also wish I read this in documentation before.

    HomeScreen's code:

    // HomeScreen.js
    function HomeScreen({navigation}) {
      const initialXPos = Dimensions.get("window").height * 0.5 ;
      const xPos = useRef(new Animated.Value(initialXPos)).current 
      const opacity = useRef(new Animated.Value(0)).current 
      const [change, setChange] = useState(true)
    
      useEffect(() => {
        const unsubscribe = navigation.addListener("focus", comingFromBot);
        return unsubscribe;
      }, [navigation, comingFromBot]);
    
      const comingFromBot = useCallback(() => {
        xPos.setValue(initialXPos);
        opacity.setValue(0);
        Animated.parallel([
          Animated.spring(xPos, {
            toValue: 100,
            tension:3,
            useNativeDriver: true,
          }),
          Animated.timing(opacity, {
            toValue: 1,
            duration: 1000,
            useNativeDriver: true,
          }),
        ]).start();
        console.log("Animation's Fired!");
      }, [xPos, opacity, initialXPos ]);
    
      return (
        <SafeAreaView style={{flex:1}}>
          <Animated.View style={[
                styles.container,
                { transform: [{ translateY: xPos }] },
                { opacity: opacity },
              ]}>
            <Text style={{fontSize:30}}>{change ? "Home!" : "TIMMY!"}</Text>
          </Animated.View>
    
          {/* debug */}
          <View style={styles.fire}>
            <Button title="fire" onPress={() => comingFromBot()}/>
          </View>
    
          <View style={styles.change}>
            <Button title="change" onPress={() => setChange(!change)}/>
          </View>
        </SafeAreaView>
      );
    }