I have a rectangle inside React Native Navigation screen that I want to animate every time user clicks on that specific screen. Now it only plays animation once, when application renders for the first time. I saw multiple solutions that worked for other people for this kind of a problem, but none of them worked for me - the application doesn't re-render on click. What am I doing wrong?
SettingsScreen.js (animation which needs to rerender on screen switch)
const { width } = Dimensions.get("screen");
const SettingsScreen = ({navigation}) => {
const isFocused = useIsFocused();
return (
<FlatList
contentContainerStyle={style.barContainer}
data={[1, 2, 3, 4, 5]}
keyExtractor={(_, index) => index.toString()}
renderItem={() => (
<ProgressBar isFocused={isFocused} navigation={navigation} />
)}
></FlatList>
);
};
const ProgressBar = ({ navigation, isFocused }) => {
const barWidth = React.useRef(new Animated.Value(0)).current;
console.log(barWidth);
const finalWidth = width / 2;
React.useEffect(() => {
const listener = navigation.addListener("focus", () => {
Animated.spring(barWidth, {
toValue: finalWidth,
bounciness: 10,
speed: 2,
useNativeDriver: false,
}).start();
});
return listener;
}, [navigation]);
return (
<View style={style.contentContainer}>
<Animated.View style={[style.progressBar, { width: barWidth }]} />
</View>
);
};
MainContainer.js (where all the navigation is set up)
const profileName = "Profile";
const detailsName = "Details";
const settingsName = "Settings";
const profileNameFR = "P";
const detailsNameFR = "D";
const settingsNameFR = "S";
const Tab = createBottomTabNavigator();
export default function MainContainer() {
const { locale } = useContext(LanguageContext);
return (
<NavigationContainer>
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
let rn = route.name;
if (rn === profileName || rn === profileNameFR) {
iconName = focused ? "person" : "person-outline";
} else if (rn === detailsName || rn === detailsNameFR) {
iconName = focused ? "list" : "list-outline";
} else if (rn === settingsName || settingsNameFR) {
iconName = focused ? "settings" : "settings-outline";
}
return <Ionicons name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: "tomato",
inactiveTintColor: "grey",
tabBarStyle: { padding: 10, height: 60 },
tabBarLabelStyle: { paddingBottom: 10, fontSize: 10 },
style: { padding: 10 },
})}
>
This is happening because FlatList is a PureComponent, meaning that it will not re-render if props
remain the same. So in this case try to use useIsFocused
hook inside the SettingsScreen
component and pass it to ProgressBar
as a prop.
Inside SettingsScreen:
const isFocused = useIsFocused();
...
<FlatList
...
renderItem={() => <ProgressBar isFocused={isFocused} />}
Also it's not recommended to use useIsFocused
hook because this will unnecessary re-render your component many times. Instead you can use an event listener to listen to 'focus' event, like this:
React.useEffect(() => {
const listener = navigation.addListener('focus', () => {
Animated.spring(barWidth, {
toValue: finalWidth,
bounciness: 10,
speed: 2,
useNativeDriver: false,
}).start();
});
return listener;
}, [navigation])