I want to achieve this animation using section list on scroll in react native.
Here is an example of food panda. This is their initial screen:
On scrolling the section list their header collapse and it becomes like this:
Here is the code of my RestaurantDetail screen:
export default function RestaurantDetail({navigation, route}) {
const headerHeight = 400;
const headerFinalHeight = 100;
const scrollY = useRef(new Animated.Value(0)).current;
const offset = headerHeight - headerFinalHeight;
const translateHeader = scrollY.interpolate({
inputRange: [0, offset],
outputRange: [0, -offset],
extrapolate: 'clamp',
});
useEffect(() => {
return navigation.addListener('focus', () => {
getRestaurantDetail();
if (user) {
getCart();
}
});
}, []);
const onScrollY = e => {
scrollY.setValue(e.nativeEvent.contentOffset.y);
headerHeight - e.nativeEvent.contentOffset.y;
console.log('Height', scrollY);
};
const onScroll = ({viewableItems}) => {
setActiveTab(viewableItems[0].section.title);
console.log('viewableItems', viewableItems[0].section.title);
};
return (
<View style={{backgroundColor: 'white', flex: 1}}>
<Animated.View
style={{
backgroundColor: 'white',
height: headerHeight,
position: 'absolute',
transform: [{translateY: translateHeader}],
}}>
<Header restaurantDetail={detail} />
<View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
<MainImage restaurantDetail={detail} />
<TouchableOpacity
onPress={() => navigation.navigate('RestaurantInfo', detail)}>
<Rating />
</TouchableOpacity>
</View>
<TouchableOpacity
onPress={() => navigation.navigate('RestaurantInfo', detail)}>
<Title restaurantDetail={detail} />
</TouchableOpacity>
<HeaderTabs
activeTab={activeTab}
setActiveTab={setActiveTab}
data={itemList}
ref={ref}
/>
</Animated.View>
<View style={{paddingTop: headerHeight}}>
<SectionListeg
restaurantDetail={detail}
activeTab={activeTab}
setActiveTab={setActiveTab}
navigation={navigation}
onScroll={onScroll}
onScrollY={onScrollY}
data={itemList}
scrollEventThrottle={16}
/>
</View>
{showView && (
<View style={styles.box}>
<View style={styles.viewCartView}>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => navigation.navigate('Cart', true)}>
<View style={styles.viewCartBtn}>
<View style={styles.counterView}>
<Text style={styles.text}>
{cart.cart ? cart.cart.length : cart.length}
</Text>
</View>
<Text style={styles.text}>View your cart</Text>
<Text style={styles.text}>{'Rs. ' + cart.sub_total}</Text>
</View>
</TouchableOpacity>
</View>
</View>
)}
<SafeAreaView
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
backgroundColor: transparent ? 'transparent' : 'white',
}}>
<TopBar navigation={navigation} shouldShow={shouldShow} name={name} />
</SafeAreaView>
</View>
);
}
const styles = StyleSheet.create({
viewCartView: {
width: '100%',
height: 100,
backgroundColor: 'white',
position: 'absolute',
bottom: 0,
borderTopRightRadius: 10,
borderTopLeftRadius: 10,
alignItems: 'center',
justifyContent: 'center',
elevation: 5,
},
headerContainer: {
// position: 'absolute',
},
box: {
// ...
shadowColor: '#000',
shadowOffset: {width: 0, height: 2},
shadowOpacity: 1.0,
shadowRadius: 3,
elevation: 5,
},
viewCartBtn: {
width: '90%',
height: 50,
backgroundColor: Colors.secondary,
alignSelf: 'center',
borderRadius: 10,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-evenly',
},
counterView: {
width: 30,
height: 30,
borderRadius: 50,
borderWidth: 1.5,
borderColor: 'white',
justifyContent: 'center',
alignItems: 'center',
},
text: {color: 'white', fontSize: 16, fontWeight: '700'},
});
Here is the code of my Section List:
const SectionListeg = ({
navigation,
onScroll,
data,
scrollEventThrottle,
activeTab,
onScrollY,
setIndex,
setActiveTab,
restaurantDetail,
shouldShow,
branchId,
}) => {
const headerHeight = 400;
const headerFinalHeight = 100;
const offset = headerHeight - headerFinalHeight;
const scrollY = useRef(new Animated.Value(0)).current;
const translateHeader = scrollY.interpolate({
inputRange: [0, offset],
outputRange: [0, -offset],
extrapolate: 'clamp',
});
const renderItem = ({item, index}) => {
return (
<Item
item={item}
navigation={navigation}
restaurantDetail={restaurantDetail}
/>
);
};
return (
<SectionList
sections={data}
showsVerticalScrollIndicator={false}
scrollEnabled={true}
keyExtractor={(item, index) => {
// console.log('Key Index', item.title);
if (item.id) {
return item.id;
}
}}
renderItem={renderItem}
onViewableItemsChanged={onScroll}
onScroll={onScrollY}
stickySectionHeadersEnabled={false}
renderSectionHeader={({section: {title}}) => (
<Text style={styles.header}>{title}</Text>
)}
/>
);
};
const Item = ({item, navigation, restaurantDetail}) => (
<View style={styles.item}>
<TouchableOpacity
activeOpacity={0.7}
style={{flexDirection: 'row', width: '100%'}}
onPress={() => onItemPress(item, navigation, restaurantDetail)}>
<View style={{flexDirection: 'row'}}>
<View style={{width: '70%'}}>
<Text style={styles.title}>{item.title}</Text>
<Text numberOfLines={2} style={styles.description}>
{item.description}
</Text>
<Text style={{marginTop: 5, color: Colors.black}}>
PKR {item.price}
</Text>
</View>
<View style={{width: '30%'}}>
<Image
style={styles.image}
source={{uri: 'https://uat.yallafood.pk/' + item.imageUrl}}
/>
</View>
</View>
<View
style={{
width: '100%',
height: 1,
position: 'absolute',
bottom: 0,
left: 0,
backgroundColor: Colors.grey,
}}
/>
</TouchableOpacity>
</View>
);
const styles = StyleSheet.create({
container: {
flex: 1,
marginHorizontal: 16,
},
item: {
backgroundColor: '#fff',
marginVertical: 5,
paddingStart: 20,
paddingEnd: 20,
flexDirection: 'row',
justifyContent: 'space-between',
},
header: {
fontSize: 26,
backgroundColor: '#fff',
marginLeft: 20,
fontWeight: 'bold',
color: 'black',
},
title: {
fontSize: 20,
color: 'black',
},
description: {
fontSize: 14,
color: Colors.grey,
marginTop: 5,
// width: '50%',
},
image: {
width: 80,
height: 80,
borderRadius: 10,
marginBottom: 10,
alignSelf: 'flex-end',
},
});
This is my initial Screen:
This is my screen on Scroll:
Any help will be appreciated.
Related
I'm using a flatlist with rowNumber={4} and I want to display some elements with a Width-max.
But I got this result :
As you can see some elements protrude on the left side. How can I make it fit automatically ?
Expected result :
There is my code :
<SafeAreaView>
<View
style={{
height: "100%",
maxWidth: "100%",
right: "10%",
}}
>
<FlatList
style={{width: "120%"}}
data={brands}
numColumns={4}
keyExtractor={(_, item) => item}
renderItem={({item}) => (
<View style={styles.card} key={item["id"]}>
<TouchableOpacity
onPress={() => {
var index = brandId.indexOf(item["id"]);
if (index > -1) {
brandId.splice(index, 1);
cpt = cpt - 1;
} else {
brandId.push(item["id"]);
cpt = cpt + 1;
}
console.log("ici ! ", cpt);
console.log("Il existe deja bro", brandId);
}}
>
<Text style={styles.text}>{item["name"]}</Text>
</TouchableOpacity>
</View>
)}
/>
</View>
</SafeAreaView>
CSS :
card: {
height: 30,
justifyContent: "center",
backgroundColor: "#E4E4E4",
margin: 5,
borderRadius: 10,
},
text: {
paddingRight: "2%",
textAlign: "center",
width: "100%",
fontSize: 12,
color: "black",
},
don't use flatlist. with flatlist you will always have fixed numberOfColumns but your item widths could be diff from each other. use map instead inside scrollview.
updated code :
<View style={[{ flexDirection: 'row', flexWrap: 'wrap' }]}>
{brands.map(element => {
return <View style={styles.card}>
<Text>{element.name}</Text>
</View>
})
}
</View>
styles:
card: {
height: 30,
justifyContent: "center",
backgroundColor: "#E4E4E4",
margin: 5,
borderRadius: 10,
paddingHorizontal: 5
},
Modify style of card:
card: {
flex:1,
flexDirection:"row"
height: 30,
justifyContent: "center",
backgroundColor: "#E4E4E4",
margin: 5,
borderRadius: 10,
},
or If you don't use flatlist then below code will solve your issue:
<View style={[{flexDirection: 'row', flexWrap: ' wrap'}]}>
data.map(element => {
return <Text style={{height:100}}>element.content</Text>
})
</View>
I'm new to React native, I'm creating a online store app my problem is : when the person selects a repeated item it does not appear in the cart it just updates , I would like it to include and list in the cart this repeated item ... as I said I'm new to react native, but I believe the problem is in the listing and not in the register ...
Function add in cart in AddCart.js
const addToCart = async (id) => {
let itemArray = await AsyncStorage.getItem('cartItems');
itemArray = JSON.parse(itemArray);
if (itemArray) {
let array = itemArray;
array.push(id);
try {
await AsyncStorage.setItem('cartItems', JSON.stringify(array));
ToastAndroid.show(
'Item Added Successfully to cart',
ToastAndroid.SHORT,
);
navigation.navigate('Home');
} catch (error) {
return error;
}
} else {
let array = [];
array.push(id);
try {
await AsyncStorage.setItem('cartItems', JSON.stringify(array));
ToastAndroid.show(
'Item Added Successfully to cart',
ToastAndroid.SHORT,
);
navigation.navigate('Home');
} catch (error) {
return error;
}
}
};
MyCart.js
import React, {useState, useEffect} from 'react';
import {
View,
Text,
TouchableOpacity,
Image,
Alert
} from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
const MyCart = ({navigation}) => {
const [product, setProduct] = useState();
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
getDataFromDB();
});
return unsubscribe;
}, [navigation]);
const getDataFromDB = async id =>{
let items = await AsyncStorage.getItem('cartItems');
items = JSON.parse(items);
if(items.length===0){
Alert.alert(
"Ops",
"The cart is empty",
[
{
text: "Ok",
onPress: () => navigation.navigate('Home'),
style: "cancel"
}
]
);
}else{
let productData = [];
if (items) {
Items.forEach(data => {
if (items.includes(data.id)) {
productData.push(data);
return;
}
});
setProduct(productData);
getTotal(productData);
} else {
setProduct(false);
getTotal(false);
}
}
};
const renderProducts = (data, index) => {
return (
<TouchableOpacity
key={data.key}
onPress={() => navigation.navigate('ProductInfo', {productID: data.id})}
style={{
width: '100%',
height: 100,
marginVertical: 6,
flexDirection: 'row',
alignItems: 'center',
}}>
<View
style={{
width: '30%',
height: 100,
padding: 14,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: COLOURS.backgroundLight,
borderRadius: 10,
marginRight: 22,
}}>
<Image
source={data.productImage}
style={{
width: '100%',
height: '100%',
resizeMode: 'contain',
}}
/>
</View>
<View
style={{
flex: 1,
height: '100%',
justifyContent: 'space-around',
}}>
<View style={{}}>
<Text
style={{
fontSize: 14,
maxWidth: '100%',
color: COLOURS.black,
fontWeight: '600',
letterSpacing: 1,
}}>
{data.productName},
</Text>
<View
style={{
marginTop: 4,
flexDirection: 'row',
alignItems: 'center',
opacity: 0.6,
}}>
<Text
style={{
fontSize: 14,
fontWeight: '400',
maxWidth: '85%',
marginRight: 4,
}}>
R$ {data.productPrice2}.00 , {data.id}
</Text>
<Text>
</Text>
</View>
</View>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
}}>
<View
style={{
borderRadius: 100,
marginRight: 20,
padding: 4,
borderWidth: 1,
borderColor: COLOURS.backgroundMedium,
opacity: 0.5,
}}>
<MaterialCommunityIcons
name="minus"
style={{
fontSize: 16,
color: COLOURS.backgroundDark,
}}
/>
</View>
<Text>1</Text>
<View
style={{
borderRadius: 100,
marginLeft: 20,
padding: 4,
borderWidth: 1,
borderColor: COLOURS.backgroundMedium,
opacity: 0.5,
}}>
<MaterialCommunityIcons
name="plus"
style={{
fontSize: 16,
color: COLOURS.backgroundDark,
}}
/>
</View>
</View>
<TouchableOpacity onPress={() => removeItemFromCart(data.id)}>
<MaterialCommunityIcons
name="delete-outline"
style={{
fontSize: 16,
color: COLOURS.backgroundDark,
backgroundColor: COLOURS.backgroundLight,
padding: 8,
borderRadius: 100,
}}
/>
</TouchableOpacity>
</View>
</View>
</TouchableOpacity>
);
};
return (
<View> {product ? product.map(renderProducts) : null} </View>
);
};
export default MyCart;
I would be grateful to help me
I want to center a button horizontally, this is my code:
return (
<View style={styles.root}>
<View style={styles.container}>
<SafeAreaView style={HEADER}>
<SliderBox
style={{ height: '100%' }}
currentImageEmitter={index => setCurrentSliderNumber(index)}
images={data.images}>
</SliderBox>
</SafeAreaView>
{currentSliderNumber == data.images.length - 1 ? <View style={styles.item}>
<Button
style={styles.getStartedButton}
appearance="ghost"
status="control"
onPress={onGetStartedPressed}
>
Get Started
</Button>
</View> : null}
</View>
</View>
)
and this is my styling:
const styles = StyleSheet.create({
forgotPasswordButton: {
paddingHorizontal: 0,
},
root: {
flex: 1,
height: '100%',
backgroundColor: '#fff'
},
container: {
borderColor: 'rgba(0,0,0,0.2)',
},
getStartedButton: {
marginVertical: 12,
backgroundColor: 'red'
},
item: {
borderColor: 'rgba(0,0,0,0.2)',
position: 'absolute',
top: '80%',
left: '50%'
}, text: {
position: 'absolute',
top: '30%',
left: '10%'
}
})
I also tried to add alingnItems:center to root:
root: {
flex: 1,
height: '100%',
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
}
But the result is not the one expected:
Try changing the item style object to this:
item: {
borderColor: 'rgba(0,0,0,0.2)',
position: 'absolute',
top: '80%',
left:0,
width:"100%",
alignItems:"center",
justifyContent:"center"
},
Do not use position until it's necessary you should use flex properties.
return (
<View style={styles.root}>
<View style={styles.container}>
<SafeAreaView style={HEADER}>
<SliderBox
style={{ height: '100%' }}
currentImageEmitter={index => setCurrentSliderNumber(index)}
images={data.images}>
</SliderBox>
</SafeAreaView>
{currentSliderNumber == data.images.length - 1 ? <View style={styles.item}>
<Button
style={styles.getStartedButton}
appearance="ghost"
status="control"
onPress={onGetStartedPressed}
>
Get Started
</Button>
</View> : null}
</View>
</View>
)
Styles
const styles = StyleSheet.create({
forgotPasswordButton: {
paddingHorizontal: 0,
},
root: {
flex: 1,
height: '100%',
backgroundColor: '#fff'
},
container: {
borderColor: 'rgba(0,0,0,0.2)',
flexGrow:1,
justifyContent: "space-between"
},
getStartedButton: {
marginVertical: 12,
backgroundColor: 'red',
alignSelf:"center",
flexGrow:2,
},
item: {
borderColor: 'rgba(0,0,0,0.2)',
flexGrow:1,
justifyContent:"center",
alignItem:"center",
}, text: {
}
})
Check out the layout docs
Hello everyone I'm having trouble with component and I get an undefined error message.So my app has 2 screen,the first one has a list of imagebackgrounds and when you press on one of the images you get a description of that image on another screen.So on this second screen I get the image that I pressed on in an image component (not background).
The problem I'm having is that when I save I get the undefined error.
First screen component :
const MealItems = (props) => {
return (
<View style={styles.main}>
<TouchableOpacity onPress={props.onSelectMeal}>
<View>
<View style={{ ...styles.details, ...styles.maintitle }}>
<ImageBackground
//resizeMode={"center"}
source={{ uri: props.image }}
style={styles.imagebg}
>
<View style={styles.textContainer}>
<Text style={styles.title} numberOfLines={1}>
{props.title}
</Text>
</View>
</ImageBackground>
</View>
<View style={{ ...styles.details, ...styles.info }}>
<Text>{props.duration}</Text>
<Text>{props.complexity.toUpperCase()}</Text>
<Text>{props.affordability.toUpperCase()}</Text>
</View>
</View>
</TouchableOpacity>
</View>
);
};
styles = StyleSheet.create({
main: {
height: 200,
width: "100%",
backgroundColor: "#f5f5f5",
borderRadius: 20,
overflow: "hidden",
marginVertical: 10,
},
maintitle: {
height: "85%",
},
title: {
fontSize: 20,
color: "white",
textAlign: "center",
},
details: {
flexDirection: "row",
},
imagebg: {
width: "100%",
height: "100%",
},
info: {
backgroundColor: "gray",
paddingHorizontal: 10,
justifyContent: "space-between",
alignItems: "center",
height: "15%",
},
textContainer: {
paddingHorizontal: 12,
paddingVertical: 10,
backgroundColor: "rgba(0,0,0,0.3)",
},
});
export default MealItems;
***Second screen file:***
const howToCook = (props) => {
const availableMeals = useSelector((state) => state.Meals.filteredMeals);
const mealId = props.navigation.getParam("mealId");
const selectedMeal = availableMeals.find((meal) => mealId === meal.id);
return (
<ScrollView>
<Image source={{ uri: selectedMeal.imageUrl }} style={styles.image} />
<View style={styles.textDetail}>
<Text>{selectedMeal.duration}</Text>
<Text>{selectedMeal.complexity.toUpperCase()}</Text>
<Text>{selectedMeal.affordability.toUpperCase()}</Text>
</View>
<View style={styles.titlePlace}>
<Text style={styles.textTitle}>Ingredients</Text>
</View>
I have created a JS function which returns a view with some subcomponent and I am reusing the code.I want to know how can I pass a function to the component which is created by a function
const MenuItem = ({title,indicatorColor,index,onPressItem}) => (
<TouchableOpacity onPress={()=>onPressItem(index)} >
<View
style={{
paddingLeft: 20,
paddingBottom: 15,
paddingTop: 15,
flexDirection: 'row',
width: 150,
borderRightWidth: 2,
borderRightColor: 'Colors.GREY_TWO',
backgroundColor: indicatorColor,
alignItems: 'center',
}}>
<View
style={{
backgroundColor: 'black',
height: 5,
width: 5,
borderRadius: 3,
alignSelf: 'center',
}}
/>
<Text style={{fontSize: 15, left: 5,alignItems: 'center',}}>{title}</Text>
</View>
</TouchableOpacity>
);
const onMenuItemPress = (index) => {
console.log('menu selected:'.index);
}
<MenuItem title='Sort' indicatorColor='red' index='0' onPress={onMenuItemPress} />
Above code throws and an error saying onMenuPress is not a function,please suggest.
const MenuItem = ({title,indicatorColor,index,onPressItem}) => (
<TouchableOpacity onPress={()=>onPressItem(index)} >
<View
style={{
paddingLeft: 20,
paddingBottom: 15,
paddingTop: 15,
flexDirection: 'row',
width: 150,
borderRightWidth: 2,
borderRightColor: 'Colors.GREY_TWO',
backgroundColor: indicatorColor,
alignItems: 'center',
}}>
<View
style={{
backgroundColor: 'black',
height: 5,
width: 5,
borderRadius: 3,
alignSelf: 'center',
}}
/>
<Text style={{fontSize: 15, left: 5,alignItems: 'center',}}>{title}</Text>
</View>
</TouchableOpacity>
);
const onMenuItemPress = (index) => {
console.log('menu selected:'.index);
}
<MenuItem title='Sort' indicatorColor='red' index='0' onPressItem={onMenuItemPress} />
You should use onPressItem and not onPress in props
Wrap the function inside the Component you are returning:
const MenuItem = ({title,indicatorColor,index,onPressItem}) => {
const onMenuItemPress = (index) => {
console.log('menu selected:'.index);
}
return (
<TouchableOpacity onPress={()=>onPressItem(index)} >
<View
style={{
paddingLeft: 20,
paddingBottom: 15,
paddingTop: 15,
flexDirection: 'row',
width: 150,
borderRightWidth: 2,
borderRightColor: 'Colors.GREY_TWO',
backgroundColor: indicatorColor,
alignItems: 'center',
}}>
<View
style={{
backgroundColor: 'black',
height: 5,
width: 5,
borderRadius: 3,
alignSelf: 'center',
}}
/>
<Text style={{fontSize: 15, left: 5,alignItems: 'center',}}>{title}
</Text>
</View>
</TouchableOpacity>
)
};
<MenuItem title='Sort' indicatorColor='red' index='0' onPress={onMenuItemPress} />