0

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)
    }
)}
Timothy Oliver
  • 470
  • 9
  • 19

1 Answers1

0

After placing the scrollY view in state, everything seemed to work as expected.

Timothy Oliver
  • 470
  • 9
  • 19