The home screen of my react native app has components which animate on scroll. However, any little change to the screen resets the components to their default state and they don't animate anymore.
I am attempting to fetch data and display it on the home screen. Whenever new content is loaded, the animated components go back to their original state.
home/index.tsx
import React, { useEffect } from 'react'
import { View, StyleSheet, StatusBar, Image } from 'react-native'
import Animated, { Extrapolate } from 'react-native-reanimated'
import { useDispatch, useSelector } from 'react-redux'
import { bannerImage, logoImage } from '../../assets/images'
import CategoryContainer from './CategoryContainer'
import colors from '../../styles/colors'
import CstmBigDisplayButton from '../../components/CstmBigDisplayButton'
import globalStyles from '../../styles/globals'
import MenuButton from '../../components/MenuButton'
import TranslucentStatusBar from '../../components/TranslucentStatusBar'
import { getCategories } from '../../redux/actions/categoryAction'
import { RootState } from '../../redux/types'
const HEADER_MAX_HEIGHT = 430
const HEADER_MIN_HEIGHT = 100
const Home = ({ navigation }: any) => {
const { categories } = useSelector((state: RootState) => state.categories)
const dispatch = useDispatch()
useEffect(() => {
dispatch(getCategories())
}, [])
let scrollY = new Animated.Value(0)
const headerHeight = Animated.interpolate(
scrollY,
{
inputRange: [0, HEADER_MAX_HEIGHT],
outputRange: [HEADER_MAX_HEIGHT, HEADER_MIN_HEIGHT],
extrapolate: Extrapolate.CLAMP,
}
)
const animateOverlay = Animated.interpolate(
scrollY,
{
inputRange: [0, HEADER_MAX_HEIGHT / 2, HEADER_MAX_HEIGHT - 30],
outputRange: [1, 0.5, 0.1],
extrapolate: Extrapolate.CLAMP,
}
)
const menuOpacity = Animated.interpolate(
scrollY,
{
inputRange: [0, HEADER_MAX_HEIGHT - 30],
outputRange: [0.5, 1],
extrapolate: Extrapolate.CLAMP
}
)
return (
<>
<TranslucentStatusBar />
<Animated.View
style={[
styles.header,
{
height: headerHeight,
elevation: 10
}
]}
>
<View style={styles.headerBackground} >
<Animated.View
style={{
position: 'absolute',
top: 0,
right: 0,
zIndex: 11,
marginTop: StatusBar.currentHeight,
opacity: menuOpacity,
}}
>
<View style={{ margin: 16 }}>
<MenuButton onPress={() => {navigation.openDrawer()}}/>
</View>
</Animated.View>
<Animated.Image
source={bannerImage}
resizeMode='cover'
style={{
position: 'absolute',
left: 0,
right: 0,
top: 0,
height: headerHeight,
opacity: animateOverlay,
}}/>
<Animated.View
style={[
styles.overlay,
{
backgroundColor: animateOverlay
}
]}
>
<Image
source={logoImage}
style={styles.logo}
resizeMode='contain'
/>
</Animated.View>
</View>
</Animated.View>
<Animated.ScrollView
scrollEventThrottle={16}
style={globalStyles.screenDefaults}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
{
useNativeDriver: true,
listener: (event: any) => console.log(event)
}
)}
>
<View style={styles.contentContainer}>
<CategoryContainer
titleStyle={[styles.general]}
titleText='General Categories'
titleTextStyle={{ color: colors.primary["500TextColor"] }}
categories={categories.filter((category: any) => !category.special)}
navigation={navigation}
/>
<View style={styles.divider}></View>
<CategoryContainer
titleStyle={[styles.special]}
titleText='Special Categories'
titleTextStyle={{ color: colors.secondary["700TextColor"] }}
categories={categories.filter((category: any) => category.special)}
navigation={navigation}
/>
</View>
<CstmBigDisplayButton />
</Animated.ScrollView>
</>
)
}
const styles = StyleSheet.create({
container: {
flex: 1
},
header: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
height: HEADER_MAX_HEIGHT,
backgroundColor: 'grey',
zIndex: 10,
alignContent: 'center',
justifyContent: 'center'
},
headerBackground: {
width: '100%',
flex: 1,
backgroundColor: '#FFF'
},
logo: {
flex: 1,
height: undefined,
width: undefined,
},
overlay: {
flex: 1,
paddingTop: StatusBar.currentHeight
},
contentContainer: {
flexDirection: 'row',
flex: 1,
paddingTop: HEADER_MAX_HEIGHT,
paddingBottom: 24
},
general: {
backgroundColor: colors.primary[500],
color: colors.primary["500TextColor"]
},
special: {
backgroundColor: colors.secondary[700],
color: colors.secondary["700TextColor"]
},
divider: {
backgroundColor: '#909090',
height: '99%',
width: '0.1%'
},
})
export default Home
globals.ts
import { StyleSheet, StatusBar } from 'react-native'
import theme, { standardInterval } from './theme'
const globalStyles = StyleSheet.create({
gutterBottom: {
marginBottom: 8
},
captionText: {
fontSize: 12
},
screenDefaults: {
flex: 1,
backgroundColor: theme.pageBackgroundColor,
},
headlessScreenDefaults: {
paddingTop: StatusBar.currentHeight,
padding: 16
},
iconText: {
flexDirection: 'row',
alignItems: 'center'
},
iconTextMargin: {
marginLeft: 4
},
label: {
fontSize: 16,
marginBottom: 4,
paddingLeft: 12,
color: '#555'
},
textInput: {
padding: 0,
fontSize: 16,
color: '#444',
marginLeft: 6,
marginRight: 8,
flex: 1
},
textInputContainer: {
borderWidth: 1,
borderColor: '#ddd',
backgroundColor: 'white',
borderRadius: standardInterval(0.5),
padding: 8,
flexDirection: 'row',
alignItems: 'center'
},
helperText: {
marginTop: 4,
paddingLeft: 12,
fontSize: 12,
color: '#555'
},
button: {
padding: standardInterval(1.5),
borderRadius: standardInterval(.5),
// marginLeft: 12,
},
})
export default globalStyles
Points worth noting. When the content is fetched from the drawer navigation component, the animated components work just fine. This is not reliable since if the data is received when the home screen is already mounted, the components will not animate.
DrawerNavigation.tsx
import React, { useEffect } from 'react'
import { createDrawerNavigator, DrawerContentScrollView, DrawerItemList, DrawerItem, DrawerContentComponentProps, } from '@react-navigation/drawer'
import { NavigationContainer } from '@react-navigation/native'
import { useSelector, useDispatch } from 'react-redux'
import AuthNavigator from './AuthNavigator'
import MainNavigator from './MainNavigator'
import theme from '../styles/theme'
import colors from '../styles/colors'
import Profile from '../screens/profile'
import { RootState } from '../redux/types'
import { verifyToken } from '../redux/actions/authAction'
import Splash from '../screens/splash'
import { logOut } from '../redux/actions/authAction'
import { getMetadata } from '../redux/actions/metadataActions'
import { getCategories } from '../redux/actions/categoryAction'
const Drawer = createDrawerNavigator()
const DrawerNavigator = () => {
const dispatch = useDispatch()
const { hasInitiallyLoaded, authenticationToken, authenticatedUser } = useSelector((state: RootState) => state.auth)
useEffect(() => {
if (!hasInitiallyLoaded) dispatch(verifyToken(authenticationToken))
})
useEffect(() => {
dispatch(getMetadata())
dispatch(getCategories())
}, [getMetadata])
return (
<>
{
hasInitiallyLoaded
? <NavigationContainer>
<Drawer.Navigator
drawerStyle={{ backgroundColor: theme.pageBackgroundColor }}
drawerContentOptions={{ activeTintColor: colors.secondary[500] }}
drawerContent={(props: DrawerContentComponentProps) => (
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
{
authenticatedUser
&& <DrawerItem
label='log out'
onPress={() => {dispatch(logOut([() => props.navigation.navigate('home')]))}}
/>
}
</DrawerContentScrollView>
)}
>
<Drawer.Screen name='home' component={MainNavigator} />
{
authenticatedUser
? <Drawer.Screen name='profile' component={Profile} />
: <Drawer.Screen name='login' component={AuthNavigator} />
}
</Drawer.Navigator>
</NavigationContainer>
: <Splash />
}
</>
)
}
export default DrawerNavigator
Also, the listener in Animated.event()
in the Animated.ScrollView
does not work
Animated.event(
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
{
useNativeDriver: true,
listener: (event: any) => console.log(event)
}
)}