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)
}
)}
After placing the scrollY view in state, everything seemed to work as expected.
Related
I'm trying to use a useRef hook so a scrollview and my pan gesture handler can share a common ref. but once I initialize the useRef() hook and pass it to both components, it breaks with this error
TypeError: Attempted to assign to readonly property.
I've tried typecasting and adding types to the useRef call but it returns the same error. Can someone help please?
My component:
import { StyleSheet, Text, View, Image, Dimensions } from "react-native";
import React from "react";
import {
PanGestureHandler,
PanGestureHandlerGestureEvent,
PanGestureHandlerProps,
} from "react-native-gesture-handler";
import Animated, {
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming,
} from "react-native-reanimated";
import { FontAwesome } from "#expo/vector-icons";
export interface InfluencerItemProps
extends Pick<PanGestureHandlerProps, "simultaneousHandlers"> {
id?: string;
name: string;
userName: string;
profileImg: string;
rating?: Number;
onDismiss?: (Item: InfluencerItemProps) => void;
}
const ITEM_HEIGHT = 65;
const { width: SCREEN_WIDTH } = Dimensions.get("window");
const TRANSLATE_X_THRESHOLD = -SCREEN_WIDTH * 0.3;
const InfluencerItem = (props: InfluencerItemProps) => {
const { name, userName, profileImg, onDismiss, simultaneousHandlers } = props;
const translateX = useSharedValue(0);
const marginVertical = useSharedValue("2%");
const R_Height = useSharedValue(ITEM_HEIGHT);
const opacity = useSharedValue(1);
const panGesture = useAnimatedGestureHandler<PanGestureHandlerGestureEvent>({
onActive: (event) => {
translateX.value = event.translationX;
},
onEnd: () => {
const shouldbeDismissed = translateX.value < TRANSLATE_X_THRESHOLD;
if (shouldbeDismissed) {
translateX.value = withTiming(-SCREEN_WIDTH);
R_Height.value = withTiming(0);
marginVertical.value = withTiming("0%");
opacity.value = withTiming(0, undefined, (isFinished) => {
if (isFinished && onDismiss) {
runOnJS(onDismiss)(props);
}
});
} else {
translateX.value = withTiming(0);
}
},
});
const rStyle = useAnimatedStyle(() => ({
transform: [
{
translateX: translateX.value,
},
],
}));
const rIconContainerStyle = useAnimatedStyle(() => {
const opacity = withTiming(
translateX.value < TRANSLATE_X_THRESHOLD ? 1 : 0
);
return { opacity };
});
const RContainerStyle = useAnimatedStyle(() => {
return {
height: R_Height.value,
opacity: opacity.value,
marginVertical: marginVertical.value,
};
});
return (
<Animated.View style={[styles.wrapper, RContainerStyle]}>
<Animated.View style={[styles.iconContainer, rIconContainerStyle]}>
<FontAwesome name="trash" size={ITEM_HEIGHT * 0.5} color="white" />
</Animated.View>
<PanGestureHandler
simultaneousHandlers={simultaneousHandlers}
onGestureEvent={panGesture}
>
<Animated.View style={[styles.container, rStyle]}>
<Image
source={{
uri: profileImg,
}}
style={styles.image}
/>
<View style={styles.text}>
<Text style={styles.name}>{name}</Text>
<Text style={styles.userName}>{userName}</Text>
</View>
</Animated.View>
</PanGestureHandler>
</Animated.View>
);
};
export default InfluencerItem;
const styles = StyleSheet.create({
wrapper: {
width: "100%",
alignItems: "center",
},
container: {
flexDirection: "row",
borderWidth: 1,
borderRadius: 12,
height: ITEM_HEIGHT,
width: "100%",
backgroundColor: "#FFFFFF",
},
image: {
marginVertical: 10,
marginHorizontal: "4%",
height: 48,
width: 48,
borderRadius: 50,
},
text: {
justifyContent: "center",
alignItems: "flex-start",
marginHorizontal: 6,
},
name: {
fontSize: 14,
fontWeight: "500",
color: "#121212",
},
userName: {
fontSize: 12,
fontWeight: "400",
color: "#121212",
},
iconContainer: {
height: ITEM_HEIGHT,
width: ITEM_HEIGHT,
backgroundColor: "red",
position: "absolute",
right: "2.5%",
justifyContent: "center",
alignItems: "center",
},
});
InfluencerItem.defaultProps = {
name: "UserName",
userName: "userName",
profileImg:
"https://d2qp0siotla746.cloudfront.net/img/use-cases/profile-picture/template_0.jpg",
rating: "4",
};
This is my Screen:
import {
StyleSheet,
Text,
View,
SafeAreaView,
TextInput,
ScrollView,
} from "react-native";
import { StatusBar } from "expo-status-bar";
import React, { useCallback, useRef, useState } from "react";
import InfluencerItem from "../../components/InfluencerItem";
import { InfluencerItemProps } from "../../components/InfluencerItem";
// import { ScrollView } from "react-native-gesture-handler";
type Props = {};
const Saved = (props: Props) => {
const [search, setSearch] = useState<string>("");
const [influencerData, setInfluencerData] = useState(influencerz);
const handleSearch = () => {
console.log(search);
};
const onDismiss = useCallback((Item: InfluencerItemProps) => {
setInfluencerData((influencers) =>
influencers.filter((item) => item.id !== Item.id)
);
}, []);
const ref = useRef(null); //useRef initialization
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={styles.container}>
<View style={styles.saved}>
{/* Saved Kikos component goes in here */}
<ScrollView ref={ref}> //passed ref here
{influencerData.map((influencer) => (
<InfluencerItem
key={influencer.id}
name={influencer.name}
userName={influencer.handle}
profileImg={influencer.image}
onDismiss={onDismiss}
simultaneousHandlers={ref} //also passed ref here
/>
))}
</ScrollView>
</View>
<Text style={styles.bottomText}>No Saved Kikos again</Text>
</View>
</SafeAreaView>
);
};
export default Saved;
const styles = StyleSheet.create({
container: {
paddingHorizontal: "4%",
},
headerText: {
color: "#121212",
fontWeight: "700",
lineHeight: 30,
fontSize: 20,
marginTop: 40,
},
search: {
borderRadius: 12,
backgroundColor: "#D9D9D9",
fontSize: 14,
lineHeight: 21,
color: "#7A7B7C",
paddingLeft: 10,
paddingRight: 5,
height: 45,
marginTop: 15,
position: "relative",
},
innerSearch: {
position: "absolute",
top: 30,
right: 10,
},
saved: {
backgroundColor: "rgba(217, 217, 217, 0.15)",
marginTop: 22,
paddingVertical: "7%",
marginBottom: 34,
},
bottomText: {
fontSize: 14,
fontWeight: "500",
textAlign: "center",
},
});
Use createRef() instead of useRef() as mentioned in the documentation.
const imagePinch = React.createRef();
return (
<RotationGestureHandler
simultaneousHandlers={imagePinch}
....
There is a complete example here in TypeScript.
Also make sure to use Animated version of components wherever applicable (<Animated.View> instead of <View>, <Animated.Image> instead of <Image> etc)
I am using FlatList in my component which is working fine. All is wanted is that my entire screen should have an automatic height to it and FlatList contents never does out of the screen height.
Also, the height of FlatList should be changed based on content/items in it rather than giving any fixed height.
How I can be acheived both cases like automatic screen height and auto height of FlatList content?
Code:
import React, { useState, useEffect } from 'react';
import {
View,
StyleSheet,
useWindowDimensions,
Text,
FlatList,
SafeAreaView,
TouchableOpacity,
} from 'react-native';
import { LinearGradient as LinearGradientView } from 'expo-linear-gradient';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { ActivityIndicator, ThemeProvider } from 'react-native-paper';
import { ScrollView } from 'react-native-gesture-handler';
import { useIsFocused, useNavigation } from '#react-navigation/native';
import Svg, { Path } from 'react-native-svg';
import { BASE_GRADIENT_HEAVY } from '../rooms/Background';
import { useResolution } from '../browse/useResolution';
import { OnboardingNavigationProp } from '../navigation/LinkingConfiguration';
import { LIGHT_THEME } from '../Theme';
import { OnboardingBack } from './OnboardingBack';
import {
useOnboardingYearsOfExperiencePage,
useOnboardingYearsOfExperienceSubmit,
} from './useOnboardingYearsOfExperiencePage';
export function OnboardingYearsOfExperience({ route }: { route?: any }) {
const { height } = useWindowDimensions();
const { top: safeAreaTop } = useSafeAreaInsets();
const [selectedId, setSelectedId] = useState<{ id: string }>();
const [role_id, setRoleId] = useState<number>();
const { navigate } =
useNavigation<OnboardingNavigationProp<'OnboardingConfirmAllDetails'>>();
const parentUrl = route?.params.result.onboarding._links;
const initialUrl = parentUrl.onboarding_years_of_experience.href;
const onboardingUrl = parentUrl.self.href;
useEffect(() => {
setRoleId(route?.params.role_id);
}, []);
const isFocused = useIsFocused();
const { contentType } = useResolution();
const {
data: result,
isLoading,
error,
} = useOnboardingYearsOfExperiencePage(initialUrl, contentType, role_id, {
enabled: isFocused,
notifyOnChangeProps: ['data', 'isLoading', 'error'],
});
const {
mutateAsync: updateYearsOfExperience,
isLoading: isUpdatingYearsOfExperience,
error: yearsOfExperienceUpdateError,
} = useOnboardingYearsOfExperienceSubmit(onboardingUrl);
const Arrow = ({ style }: { style: object }) => {
return (
<Svg width="7" height="11" viewBox="0 0 7 11" fill="none" style={style}>
<Path
d="M6.28711 5.53931C6.28711 5.31649 6.19783 5.12476 6.02452 4.95376L1.9911 1.07251C1.85455 0.937781 1.68649 0.865234 1.48692 0.865234C1.07728 0.865234 0.751664 1.18651 0.751664 1.58552C0.751664 1.78243 0.830441 1.96898 0.977493 2.11407L4.54875 5.53413L0.977492 8.95937C0.835692 9.10446 0.751663 9.28583 0.751662 9.48792C0.751662 9.88693 1.07728 10.2082 1.48692 10.2082C1.68649 10.2082 1.85455 10.1357 1.99635 10.0009L6.02452 6.11968C6.20308 5.93832 6.28711 5.75695 6.28711 5.53931Z"
fill="#4D4D4D"
/>
</Svg>
);
};
const Item = ({
item,
onPress,
}: {
item?: { id: string; title: string };
onPress: () => void;
}) => (
<TouchableOpacity onPress={onPress}>
<Text
style={{
color: '#4D4D4D',
fontFamily: 'Inter_400Regular',
fontStyle: 'normal',
fontWeight: '400',
fontSize: 16,
lineHeight: 32,
padding: 3,
margin: 2,
}}
>
{item?.title}
</Text>
</TouchableOpacity>
);
const renderItem = ({ item }: { item: { id: string; title: string } }) => {
return (
<React.Fragment>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
borderRadius: 5,
backgroundColor:
selectedId?.id === item?.id ? '#F2F2F2' : 'transparent',
}}
>
<View style={{ width: '90%' }}>
<Item
item={item}
onPress={() => {
setSelectedId({ id: item.id });
updateYearsOfExperience({ id: item.id, role_id })
.then((result) => {
navigate('Onboarding', {
screen: 'OnboardingConfirmAllDetails',
params: { result },
});
})
.catch(() => {});
}}
/>
</View>
<View style={{ width: '10%' }}>
<Arrow style={{ position: 'absolute', top: 16, right: 10 }} />
</View>
</View>
</React.Fragment>
);
};
return (
<ThemeProvider theme={LIGHT_THEME}>
<View style={{ height: safeAreaTop }} />
<View style={styles.topHeader}>
<LinearGradientView
{...BASE_GRADIENT_HEAVY}
style={[styles.gradiantStyle]}
/>
</View>
<View style={styles.innerContainer}>
<ScrollView
contentContainerStyle={{
marginHorizontal: 'auto',
alignSelf: 'center',
width: '100%',
backgroundColor: '#ffffff',
height: '100%',
}}
>
<OnboardingBack
style={{
left: -10,
}}
/>
<Text style={styles.topHeadline}>
How many years of experience do you have?
</Text>
<Text style={styles.middleHeadline}>
Jump-start the conversation by sharing your years of experience
within healthcare.
</Text>
{isLoading || isUpdatingYearsOfExperience ? (
<ActivityIndicator size="large" />
) : (
<SafeAreaView>
<FlatList
contentContainerStyle={{ flexGrow: 1 }}
data={Object.values(
result!['years_of_experiences']['years_of_experiences_list']
)}
renderItem={renderItem}
keyExtractor={(item) => item.id}
extraData={selectedId}
style={{
borderWidth: 1,
borderStyle: 'solid',
borderColor: '#D7D7D7',
borderRadius: 5,
padding: 12,
height: '27%',
}}
ListEmptyComponent={
<View>
<Text>No data found</Text>
</View>
}
/>
</SafeAreaView>
)}
</ScrollView>
</View>
</ThemeProvider>
);
}
const styles = StyleSheet.create({
innerContainer: {
display: 'flex',
position: 'relative',
backgroundColor: 'white',
padding: 18,
},
topHeader: {
backgroundColor: '#e5dede',
padding: 0,
width: '100%',
},
topHeadline: {
fontSize: 24,
fontFamily: 'CircularStd_Medium',
fontStyle: 'normal',
fontWeight: '500',
color: '#222222',
marginTop: 40.6,
marginBottom: 14.6,
display: 'flex',
alignItems: 'center',
lineHeight: 32,
},
middleHeadline: {
fontSize: 16,
fontFamily: 'Inter_400Regular',
fontStyle: 'normal',
fontWeight: '400',
lineHeight: 24,
color: '#4D4D4D',
marginBottom: 30,
},
gradiantStyle: {
width: 248.4,
height: 4.71,
},
});
add the following style to your flatList =>
style={{maxHeight:'what ever you want',flexGrow:0}}
i have a problem updating the parent state. indeed I want to change the edge of an edge after the user has clicked and chosen his level of performance in sport. after having chosen its level of performance, it is then that the selected sport takes a blue border.
please i need help
Sport.js
import React, { useState, useEffect } from "react";
import {
Modal,
Alert,
View,
SafeAreaView,
StyleSheet,
Image,
Text,
TouchableOpacity,
TouchableHighlight,
ScrollView,
} from "react-native";
import AppLoading from "expo-app-loading";
import { useFonts } from "expo-font";
import * as Font from "expo-font";
import { useNavigation } from "#react-navigation/native";
import { ProgressBar, Colors } from "react-native-paper";
import { Ionicons } from "#expo/vector-icons";
import SportItem from "../../Components/SportItem";
import HeadCSearch from "../../Components/HeadCSearch";
import SearchTitle from "../../Components/SearchTitle";
import ModalPicker from "./ModalPicker";
export default function Sport() {
const navigation = useNavigation();
const [chooseData, setchooseData] = useState("0%");
const [sports, setSports] = useState([
{
id: 1,
urlIcon: "ios-football",
name: "Football",
},
{
id: 2,
urlIcon: "hand-left-sharp",
name: "Handball",
},
{
id: 3,
urlIcon: "ios-basketball",
name: "Basketball",
},
{
id: 4,
urlIcon: "ios-car-sport",
name: "Course",
},
{
id: 5,
urlIcon: "ios-baseball",
name: "BaseBall",
},
{
id: 6,
urlIcon: "ios-tennisball",
name: "Tennis",
},
/* {
id: 7,
urlIcon:
"https://img.icons8.com/material-outlined/50/000000/basketball-net.png",
name: "Basketball",
}, */
]);
const [toggle, setToggle] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const changeModalVisibility = (bool, id) => {
setModalOpen(bool);
sports.map((sport) => {
if (id === sport.id) {
const newState = !toggle;
setToggle(newState);
}
});
};
const setData = (option) => {
setchooseData(option);
};
const OPTIONS = ["Débutant", "Moyen", "Intermédiaire", "Expert"];
let [fontsLoaded] = useFonts({
"Gilroy-ExtraBold": require("../../assets/fonts/Gilroy-ExtraBold.otf"),
"Gilroy-Light": require("../../assets/fonts/Gilroy-Light.otf"),
});
if (!fontsLoaded) {
return <AppLoading />;
} else {
const borderColorValue = toggle ? "#49B5F2" : "white";
return (
<SafeAreaView style={styles.infoCont}>
<ProgressBar
style={{ marginTop: 35, borderRadius: 10 }}
progress={1}
color="#49B5F2"
/>
<View style={styles.containWhite}>
<SearchTitle
styl={styles.whiteText}
partenaire={"Choississez vos Sports Favoris"}
/>
{/* <Modal visible={modalOpen} animationType="slide" transparent={true}>
<View style={{ flex: 1 }}>
<Text>Salut toi</Text>
</View>
</Modal> */}
<Modal
animationType="slide"
transparent={true}
visible={modalOpen}
onRequestClose={() => {
changeModalVisibility(false);
}}
>
<ModalPicker
sports={sports}
OPTIONS={OPTIONS}
setData={setData}
changeModalVisibility={changeModalVisibility}
/>
</Modal>
<ScrollView
contentContainerStyle={{
backgroundColor: "#FFFFFF",
flexWrap: "wrap",
flexDirection: "row",
/* borderColor: "yellow",
borderWidth: 3,
borderStyle: "solid", */
}}
>
{sports.map((sport) => (
<TouchableOpacity
onPress={() => {
changeModalVisibility(true, sport.id);
}}
style={{
alignItems: "center",
display: "flex",
flexDirection: "row",
bottom: 10,
width: 150,
height: 50,
margin: 10,
backgroundColor: "#E4E9DD",
borderColor: borderColorValue,
borderWidth: 1,
borderRadius: 5,
}}
key={sport.id}
>
<View
style={{
alignItems: "center",
display: "flex",
flexDirection: "row",
}}
>
<Ionicons
style={{ left: 8, top: 0 }}
name={sport.urlIcon}
size={20}
color="black"
/>
<Text
style={{
fontFamily: "Gilroy-Light",
fontWeight: "bold",
fontSize: 16,
color: "#000000",
left: 15,
}}
>
{sport.name}
</Text>
</View>
<View style={styles.rate}>
<Text style={styles.rateText}>{chooseData}</Text>
</View>
</TouchableOpacity>
))}
</ScrollView>
</View>
<TouchableOpacity
onPress={() => navigation.navigate("Search")}
style={styles.textinputcont}
>
<Text style={styles.textR}>Continuer</Text>
</TouchableOpacity>
</SafeAreaView>
);
}
}
ModalPicker.js
import React from "react";
import {
View,
Dimensions,
TouchableOpacity,
StyleSheet,
TouchableHighlight,
Text,
} from "react-native";
import { Fontisto } from "#expo/vector-icons";
import SearchTitle from "../../Components/SearchTitle";
export default function ModalPicker(props) {
const WIDTH = Dimensions.get("window").width;
const HEIGHT = Dimensions.get("window").height;
const onPressItem = (option) => {
props.changeModalVisibility(false);
props.setData(option);
};
const option = props.OPTIONS.map((item, index) => {
return (
<TouchableOpacity
style={styles.option}
key={index}
onPress={() => onPressItem(item)}
>
<Text
style={[
styles.textStyle,
{
width: 100,
height: 40,
backgroundColor: "#F7F7F7",
borderRadius: 4,
/* borderColor: "#49B5F2",
borderWidth: 1,
borderStyle: "solid", */
color: "black",
flexDirection: "row",
margin: 2,
top: 5,
flex: 1,
//top: 15,
justifyContent: "center",
alignItems: "center",
},
]}
>
{item}
</Text>
</TouchableOpacity>
);
});
return (
<TouchableOpacity
onPress={() => props.changeModalVisibility(false)}
style={styles.container}
>
<View style={styles.centeredView}>
<View
style={[styles.modalView, { width: WIDTH - 85, height: HEIGHT / 3 }]}
>
<SearchTitle
styl={[styles.whiteText, { top: -50 }]}
partenaire={"Votre Niveau"}
/>
<Text style={[styles.modalText, styles.centeredView, { top: -50 }]}>
{option}
</Text>
<TouchableOpacity
style={{
...styles.openButton,
top: -30,
left: 30,
}}
onPress={() => {
console.log("remove border color");
}}
>
<Text style={styles.textStyle}>Retirez des favoris {" "} </Text>
<Fontisto
style={{ position: "absolute", right: 0, top: 10 }}
name="minus-a"
size={20}
color="black"
/>
</TouchableOpacity>
</View>
</View>
</TouchableOpacity>
);
}
[enter image description here][1]
you need to have the styles dynamically inserted into your code.
and having an event raised from the child component and handled in the parent component, the following code can help to understand that
class Parent extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
backgroundColor: 'yellow'
}
}
onChangeStyle(backgroundColor) {
this.setState({
backgroundColor: backgroundColor
})
}
render() {
return <div style={{backgroundColor: this.state.backgroundColor, padding: 10}}>
<Child onChangeParentStyle={this.onChangeStyle.bind(this)}/>
</div>
}
}
class Child extends React.Component {
onClick() {
this.props.onChangeParentStyle('red');
}
render() {
return <span onClick={this.onClick.bind(this)} style={{background: 'white', cursor: 'pointer'}}>
Change parent style
</span>
}
}
React.render(<Parent />, document.getElementById('container'));
You need to pass from parent to child callback function, and then call it in the child.
const initState = {
show: false
}
const Parent = (props) => {
const [state, setState] = useState({ ...initState })
const updateState = (childState) => {
setState({
show: !state.show
});
}
return (
<Child {...{ updateState }} />
);
}
const Child = (props) => {
const [state, setState] = useState({})
const handleClick = () => {
// You can also pass data to parent
props.updateState(state);
}
return (
<Button onClick={handleClick}>Click Me</Button>
);
}
Run on android emulator but undefined is not an object (evaluating'_this.props).
Hello guys, I would like a help to call MainScreen through onpress =>, can someone help me see where I'm going wrong
App.js is my Frist screen and MainScreen is the Second screen
I use App to navigate to Mainscreen.
APP.JS
import React, { useState, useEffect } from "react";
import {
View,
KeyboardAvoidingView,
Image,
TextInput,
TouchableOpacity,
Text,
StyleSheet,
Animated,
} from "react-native";
import { CollectionList } from "./src/components/CollectionList";
import { CollectionCreate } from "./src/components/CollectionCreate";
import { MainScreen } from "./MainScreen";
import { firstScreenStack } from "./MainScreen";
function App(props) {
const [offset] = useState(new Animated.ValueXY({ x: 0, y: 95 }));
const [opacity] = useState(new Animated.Value(0));
useEffect(() => {
Animated.parallel([
Animated.spring(offset.y, {
toValue: 0,
speed: 4,
bounciness: 20,
}),
Animated.timing(opacity, {
toValue: 1,
duration: 550,
}),
]).start();
}, []);
props.navigation
return (
<KeyboardAvoidingView style={styles.background}>
<View style={styles.containerLogo}>
<Image source={require("./assets/logoLogin.png")} />
</View>
<Animated.View
style={[
styles.container,
{
opacity: opacity,
transform: [{ translateY: offset.y }],
},
]}
>
<TextInput
style={styles.imput}
placeholder="Email"
autoCorrect={false}
onChangeText={() => {}}
/>
<TextInput
style={styles.imput}
placeholder="Senha"
autoCorrect={false}
onChangeText={() => {}}
/>
<TouchableOpacity
style={styles.btAcess}
onPress={() => props.navigation.navigate(MainScreen)}
>
<Text style={styles.btSubText}
>Acessar</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.btCreate}>
<Text style={styles.btPassText}>Criar conta</Text>
</TouchableOpacity>
</Animated.View>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
background: {
flex: 1,
alignItems: "center",
justifyContent: "center",
backgroundColor: "#a4a4a4",
},
containerLogo: {
flex: 1,
justifyContent: "center",
},
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
width: "90%",
paddingBottom: 50,
},
imput: {
backgroundColor: "#fff",
width: "90%",
marginBottom: 15,
color: "#222",
fontSize: 17,
borderRadius: 7,
padding: 10,
},
text: {
color: "#FFF",
},
btAcess: {
backgroundColor: "#00a0cf",
width: "90%",
height: 45,
alignItems: "center",
justifyContent: "center",
borderRadius: 7,
},
btSubText: {
color: "#ffff",
fontSize: 18,
},
btCreate: {
marginTop: 8,
},
btPassText: {
color: "#FFF",
},
});
export default App;
MainScreen.js
import "react-native-gesture-handler";
import * as React from "react";
import { View, TouchableOpacity, Image, StyleSheet } from "react-native";
import Icon from "react-native-vector-icons/Ionicons";
import { CollectionList } from "./src/components/CollectionList";
import { CollectionCreate } from "./src/components/CollectionCreate";
import { NavigationContainer } from "#react-navigation/native";
import { createStackNavigator } from "#react-navigation/stack";
import { createDrawerNavigator } from "#react-navigation/drawer";
import CustomSidebarMenu from "./CustomSidebarMenu";
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
const NavigationDrawerStructure = (props) => {
const toggleDrawer = () => {
props.navigationProps.toggleDrawer();
};
return (
<View style={{ flexDirection: "row" }}>
<TouchableOpacity onPress={toggleDrawer}>
<Image
source={{
uri:
"https://raw.githubusercontent.com/AboutReact/sampleresource/master/drawerWhite.png",
}}
style={{ width: 25, height: 25, marginLeft: 5 }}
/>
</TouchableOpacity>
</View>
);
};
export default function firstScreenStack({navigation}) {
return (
<Stack.Navigator initialRouteName="FirstPage">
<Stack.Screen
name="FirstPage"
component={CollectionList}
options={{
title: "Carfix",
headerLeft: () => (
<NavigationDrawerStructure navigationProps={navigation} />
),
headerStyle: {
backgroundColor: "#007AFF",
},
headerTintColor: "#fff",
headerTitleStyle: {
fontWeight: "bold",
},
}}
/>
</Stack.Navigator>
);
}
function secondScreenStack() {
return (
<Stack.Navigator
navigation={navigation}
initialRouteName="Collection Create"
screenOptions={{
headerLeft: () => (
<NavigationDrawerStructure navigationProps={navigation} />
),
headerStyle: {
backgroundColor: "#007AFF",
},
headerTintColor: "#fff",
headerTitleStyle: {
fontWeight: "bold",
},
}}
>
<Stack.Screen
name="Collection Create"
component={CollectionCreate}
options={{
headerShown: false,
}}
/>
</Stack.Navigator>
);
}
export function MainScreen (props) {
return (
<NavigationContainer>
<Drawer.Navigator
drawerContentOptions={{
activeTintColor: "#007AFF",
itemStyle: { marginVertical: 1 },
}}
drawerContent={(props) => <CustomSidebarMenu {...props} />}
>
<Drawer.Screen
name="FirstPage"
options={{
drawerLabel: "Carfix",
drawerIcon: ({ focused, size }) => (
<Image
source={require("./assets/homeIcon.png")}
style={styles.iconStyle}
/>
),
}}
component={firstScreenStack}
/>
<Drawer.Screen
name="SecondPage"
options={{
drawerLabel: "Camera",
drawerIcon: ({ focused, size }) => (
<Image
source={require("./assets/cameraIcon.png")}
style={styles.iconStyle}
/>
),
}}
component={secondScreenStack}
/>
</Drawer.Navigator>
</NavigationContainer>
);
}
const styles = StyleSheet.create({
sideMenuProfileIcon: {
resizeMode: "center",
width: 70,
height: 70,
borderRadius: 200 / 2,
alignSelf: "center",
marginTop: 100,
},
iconStyle: {
width: 40,
height: 40,
marginBottom: 4,
marginLeft: 1,
},
customItem: {
padding: 16,
flexDirection: "row",
alignItems: "center",
marginRight: 5,
},
textSideBar: {
marginTop: 9,
},
});
On your app.js component, you do this.props.navigation.... You can't access to the component props like that.
It is a functional component so if you want to access to the props object,
function App(props) {
...
props.navigation...
}
In my current app the animated y value only changes after I hot reload my app.
Before the reload the app's y value of my component doesn't change or atleast it doesnt interpolate the y value
I'm unsure what causes this strange ocurrance.
Here is a snippet of the code.
Here I create the animated Y value
import React from "react";
import { View, StyleSheet } from "react-native";
import Animated from "react-native-reanimated";
import ListHeader from "./ListHeader";
import ListBody from "./ListBody";
const { Value } = Animated;
const TransitionList = ({ album }) => {
const y = new Value(0);
return (
<View style={styles.container}>
<ListHeader {...{ y, album }} />
<ListBody {...{ y, album }} />
</View>
);
};
export default TransitionList
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "black",
},
});
Here I change the Y value onScroll
import React from "react";
import styled from "styled-components";
import { StyleSheet, View } from "react-native";
import { onScrollEvent } from "react-native-redash";
import { LinearGradient } from "expo-linear-gradient";
import Animated from "react-native-reanimated";
import ListTitle from "./ListTitle";
import ListItem from "./ListItem";
import {
MAX_HEADER_HEIGHT,
MIN_HEADER_HEIGHT,
BUTTON_HEIGHT,
} from "../helpers";
const { interpolate, Extrapolate } = Animated;
const ListBody = ({ album: { artist, tracks }, y }) => {
const height = interpolate(y, {
inputRange: [-MAX_HEADER_HEIGHT, -BUTTON_HEIGHT / 2],
outputRange: [0, MAX_HEADER_HEIGHT + BUTTON_HEIGHT],
extrapolate: Extrapolate.CLAMP,
});
const opacity = interpolate(y, {
inputRange: [-MAX_HEADER_HEIGHT / 2, 0, MAX_HEADER_HEIGHT / 2],
outputRange: [0, 1, 0],
extrapolate: Extrapolate.CLAMP,
});
return (
<Animated.ScrollView
onScroll={onScrollEvent({ y })}
style={styles.container}
showsVerticalScrollIndicator={false}
scrollEventThrottle={1}
stickyHeaderIndices={[1]}
>
<View style={styles.cover}>
<Animated.View style={[styles.gradient, { height }]}>
<LinearGradient
style={StyleSheet.absoluteFill}
start={[0, 0.3]}
end={[0, 1]}
colors={["transparent", "rgba(0, 0, 0, 0.2)", "black"]}
/>
</Animated.View>
<View style={styles.artistContainer}>
<Animated.Text style={[styles.artist, { opacity }]}>
{artist}
</Animated.Text>
</View>
</View>
<View style={styles.header}>
<ListTitle {...{ y, artist }} />
</View>
<View style={styles.tracks}>
{tracks.map((track, key) =>
<ListItem index={key + 1} {...{ track, key, artist }} />
)}
</View>
</Animated.ScrollView>
);
};
export default ListBody
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: MIN_HEADER_HEIGHT - BUTTON_HEIGHT / 2,
},
cover: {
height: MAX_HEADER_HEIGHT - BUTTON_HEIGHT,
},
gradient: {
position: "absolute",
left: 0,
bottom: 0,
right: 0,
alignItems: "center",
},
artistContainer: {
...StyleSheet.absoluteFillObject,
justifyContent: "center",
alignItems: "center",
},
artist: {
textAlign: "center",
color: "white",
fontSize: 48,
fontWeight: "bold",
},
header: {
marginTop: -BUTTON_HEIGHT,
},
tracks: {
paddingTop: 32,
backgroundColor: "black",
},
});
Here the Y value's change should also occur in this component
import React from "react";
import Animated from "react-native-reanimated";
import { Image, StyleSheet } from "react-native";
import { MAX_HEADER_HEIGHT, HEADER_DELTA, BUTTON_HEIGHT } from "../helpers";
const { interpolate, Extrapolate } = Animated;
const ListHeader = ({ album: { cover }, y }) => {
const scale = interpolate(y, {
inputRange: [-MAX_HEADER_HEIGHT, 0],
outputRange: [4, 1],
extrapolateRight: Extrapolate.CLAMP,
});
const opacity = interpolate(y, {
inputRange: [-64, 0, HEADER_DELTA],
outputRange: [0, 0.2, 1],
extrapolate: Extrapolate.CLAMP,
});
return (
<Animated.View style={[styles.container, { transform: [{ scale }] }]}>
<Image style={styles.image} source={cover} />
<Animated.View
style={{
...StyleSheet.absoluteFillObject,
backgroundColor: "black",
opacity,
}}
/>
</Animated.View>
);
};
export default ListHeader
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
height: MAX_HEADER_HEIGHT + BUTTON_HEIGHT * 2,
},
image: {
...StyleSheet.absoluteFillObject,
width: undefined,
height: undefined,
},
});
I fixed it by removing the reanimated and react-native-redash libraries and using RN's own Animated library
Example below:
const TransitionList = ({
album,
children,
image,
title,
sub,
list,
header,
}) => {
const [scrollY, setScrollY] = useState(new Animated.Value(0));
return (
<Container SCREEN_HEIGHT={SCREEN_HEIGHT} ThemeColors={ThemeColors}>
<ListHeader y={scrollY} album={album} image={image} />
<ListBody
setY={setScrollY}
y={scrollY}
album={album}
title={title}
sub={sub}
header={header}
list={list}
>
{children}
</ListBody>
</Container>
);
};
<Animated.ScrollView
onScroll={event =>
setY(new Animated.Value(event.nativeEvent.contentOffset.y))}
style={styles.container}
showsVerticalScrollIndicator={false}
scrollEventThrottle={1}
stickyHeaderIndices={[1]}
>