when I run my application it's okay and work If I create an array and put it in the data in FlatList like this array
const photos = [
{ id: 1, title: "Photo 1" },
{ id: 2, title: "Photo 2" },
{ id: 3, title: "Photo 3" },
{ id: 4, title: "Photo 4" },
{ id: 5, title: "Photo 5" },
{ id: 6, title: "Photo 6" },
];
But when I replace the photos array with an API, The app doesn't work. I tried more than API, I think the error is in my code not in the API,
This error appears to me " scrollToIndex out of range: request index1 but maximum is -1 "
What's wrong with my code?
import React, { useState, useRef, useEffect } from "react";
import {
StyleSheet,
View,
FlatList,
Dimensions,
Text,
TouchableOpacity,
} from "react-native";
import { AntDesign } from "#expo/vector-icons";
import axios from "axios";
const phoneWidth = Dimensions.get("screen").width;
const phoneHeight = Dimensions.get("screen").height;
function ScrollScreen() {
const [index, setIndex] = useState(0);
const [border, setBorder] = useState(0);
const refContainer = useRef();
const refBox = useRef();
const [data, setData] = useState([]);
useEffect(() => {
photos();
}, []);
function photos() {
axios
.get("https://jsonplaceholder.typicode.com/photos")
.then(async function (response) {
setData(response.data);
})
.catch((err) => console.error(err));
}
useEffect(() => {
refContainer.current.scrollToIndex({ animated: true, index });
}, [index]);
useEffect(() => {
refBox.current.scrollToIndex({ animated: true, index });
}, [index]);
const theNext = () => {
if (index < photos.length - 1) {
setIndex(index + 1);
setBorder(index + 1);
}
};
const thePrevious = () => {
if (index > 0) {
setIndex(index - 1);
setBorder(index - 1);
}
};
return (
<View style={styles.con}>
<AntDesign
style={[styles.iconConPosition, { left: phoneWidth * 0.05 }]}
onPress={thePrevious}
size={55}
color="#0dddcb"
name="caretleft"
/>
<AntDesign
style={[styles.iconConPosition, { right: phoneWidth * 0.05 }]}
onPress={theNext}
size={55}
color="#0dddcb"
name="caretright"
/>
<FlatList
scrollEnabled={false}
ref={refContainer}
data={data}
// data={photos}
keyExtractor={(item, index) => item.id.toString()}
style={styles.flatList}
renderItem={({ item, index }) => (
<View
style={{
height: 150,
width: phoneWidth * 0.7,
margin: 50,
backgroundColor: "red",
alignSelf: "center",
justifyContent: "center",
alignItems: "center",
}}
>
<Text>{item.id}</Text>
<Text>{item.title}</Text>
</View>
)}
horizontal
pagingEnabled //تفعيل خاصية التمرير
showsHorizontalScrollIndicator={false}
/>
<FlatList
ref={refBox}
data={data}
// data={photos}
keyExtractor={(item, index) => item.id.toString()}
style={styles.flatList}
renderItem={({ item, index }) => (
<TouchableOpacity
onPress={() => {
setIndex(index);
setBorder(index);
}}
style={
border === index
? {
height: 100,
width: phoneWidth * 0.4,
margin: 7,
backgroundColor: "gray",
alignSelf: "center",
justifyContent: "center",
alignItems: "center",
borderWidth: 2,
borderColor: "blue",
}
: {
height: 100,
width: phoneWidth * 0.4,
margin: 7,
backgroundColor: "gray",
alignSelf: "center",
justifyContent: "center",
alignItems: "center",
}
}
>
<Text>{item.id}</Text>
<Text>{item.title}</Text>
</TouchableOpacity>
)}
horizontal
/>
<Text>{index}</Text>
</View>
);
}
export default ScrollScreen;
Initially data is an empty array, but index is set to 0. On the first invocation of the useEffect that tries to scroll, there is an error because scrollToIndex(0) is an error when data is empty, since there is no item for index 0.
Try initializing the border and index state to -1 instead of 0 like:
const [index, setIndex] = useState(-1);
const [border, setBorder] = useState(-1);
On a separate but related note the theNext function has an error, it should be checking data.length instead of photos.length.
Ran into the same issue just now. Using ScrollToOffSet({offset: number, animated: boolean}) instead of scrollToIndex solved the issue for me
Related
Hi I was working on a project, and I am on a part where I am adding up the total cost of orders in the order summary page. I have used
`
let totalPrice = Object.keys(cartItem).map((menuKey) => (
totalPrice =+ cartItem[menuKey].cost))
`
to sum up the cost and display totalPrice
I understand that it might be in String right now, although I am not sure why, because I have passed the param as a number (4.29)
Right now I have 3 items in my cart, all 4.29 for now.
As expected from a string's behavior, it is being displayed as 4.294.294.29.
Problem number 1) parseDouble not working.
Problem number 2) I have no idea how to use the toFixed(2) method on my code.
Any Idea on how I can implement the total cost method?
Whole File :
`
import {
View,
StyleSheet,
ScrollView,
Alert,
ActivityIndicator,
TouchableOpacity,
Button,
Text
} from "react-native";
import AsyncStorage from "#react-native-async-storage/async-storage";
import { useFocusEffect } from "#react-navigation/native";
import ItemCard from "../components/ItemCard";
import { useState, useCallback } from "react";
import { Swipeable } from "react-native-gesture-handler";
import { Ionicons } from "#expo/vector-icons";
export default function CartScreen({ navigation }) {
const [loading, setLoading] = useState(true);
const [cartItem, setcartItem] = useState({});
const [prevOpenedRow, setPrevOpenedRow] = useState();
const [selectedMenu, setselectedMenu] = useState({});
const CART_KEY = "#carts_Key";
const saveCart = async (menuObj) => {
try {
const jsonValue = JSON.stringify(menuObj);
await AsyncStorage.setItem(CART_KEY, jsonValue);
} catch (e) {
alert(`${title}: ${e}`);
}
};
const deleteCart = async (menuKey) => {
const newItem = { ...cartItem };
delete newItem[menuKey];
setcartItem(newItem);
await saveCart(newItem);
};
const alertBeforeDelete = (menuKeyToDelete) => {
Alert.alert(
"Remove from Cart",
`Removing "${cartItem[menuKeyToDelete].title}"`,
[
{
text: "Cancel",
},
{
text: "Remove",
onPress: () => deleteCart(menuKeyToDelete),
style: "destructive",
},
]
);
};
const clearCart = async () => {
const emptyCart = {};
setcartItem(emptyCart);
await saveCart(emptyCart);
};
const alertBeforeClear = () => {
Alert.alert(
"Clearing All Items at Cart",
"Clearing Cart Now. Are you sure?",
[
{
text: "Cancel",
},
{
text: "Yes, Clear",
onPress: () => clearCart(),
style: "destructive",
},
]
);
};
console.log(cartItem)
let totalPrice = Object.keys(cartItem).map((menuKey) => (
totalPrice =+ cartItem[menuKey].cost))
// Swipeable code modified;
const renderRightActions = (progress, dragX, alertBeforeDelete) => {
return (
<View
style={{
margin: 0,
alignContent: "center",
justifyContent: "center",
width: 70,
}}
>
<TouchableOpacity
style={styles.deleteButton}
onPress={alertBeforeDelete}
>
<Ionicons name="trash" size={40} color="#fff" />
</TouchableOpacity>
</View>
);
};
const closeRow = (menuKey) => {
if (prevOpenedRow && prevOpenedRow !== selectedMenu[menuKey]) {
prevOpenedRow.close();
}
setPrevOpenedRow(selectedMenu[menuKey]);
};
useFocusEffect(
useCallback(() => {
const getCart = async () => {
try {
const jsonValue = await AsyncStorage.getItem(CART_KEY);
setcartItem(jsonValue != null ? JSON.parse(jsonValue) : {});
} catch (e) {
alert(`${e}`);
}
};
getCart();
setLoading(false);
return () => {
};
}, [])
);
return loading ? (
<View style={styles.loadingPage}>
<ActivityIndicator size="large" color="#ffffff" />
</View>
) : (
<>
<Text style={styles.titleText}>Your Current Orders:</Text>
<View style={styles.container}>
<ScrollView>
{Object.keys(cartItem).map((menuKey) => (
<TouchableOpacity
key={menuKey}
onPress={() => {
navigation.navigate({
name: "customize",
params: {
text: cartItem[menuKey].text,
image: cartItem[menuKey].image,
cost: cartItem[menuKey].cost,
},
});
}}
>
<Swipeable
renderRightActions={(progress, dragX) => renderRightActions(progress, dragX, () => alertBeforeDelete(menuKey)
)}
ref={(ref) => (selectedMenu[menuKey] = ref)}
onSwipeableOpen={() => closeRow(menuKey)}
rightOpenValue={-100}
>
<ItemCard
text={cartItem[menuKey].text}
image={cartItem[menuKey].image}
cost ={cartItem[menuKey].cost}
/>
</Swipeable>
</TouchableOpacity>
))}
<View style={styles.priceBlock}><Text style = {styles.totalText}>Total Price: ${totalPrice}</Text></View>
<View style={styles.itemTextBlock}>
<TouchableOpacity
style={styles.clearButton}
title="clear"
color="red"
onPress={alertBeforeClear}>
<Text style={styles.buttonText}>Clear All</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.checkoutButton}
onPress={() => {
navigation.navigate('Past Orders');
clearCart();
}}
>
<Text style={styles.buttonText}>Checkout</Text>
</TouchableOpacity>
</View>
</ScrollView>
</View></>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "white",
},
loadingPage: {
flex: 1,
backgroundColor: "white",
justifyContent: "center",
},
deleteButton: {
color: "red",
backgroundColor: "#f5392f",
height: "95%",
borderRadius: 15,
justifyContent: "center",
alignItems: "center",
},
titleText: {
fontSize: 30,
fontWeight: "bold",
color: "black",
resizeMode: "contain",
textAlign: "center"
},
clearButton: {
width: "40%",
borderRadius: 25,
height: 50,
alignItems: "center",
justifyContent: "center",
backgroundColor: "grey",
},
checkoutButton: {
width: "40%",
borderRadius: 25,
height: 50,
alignItems: "center",
justifyContent: "center",
backgroundColor: "#800000",
},
buttonText: {
color: 'white',
fontWeight: 'bold'
},
itemTextBlock: {
alignItems: "center",
flex: 1,
flexDirection: "row",
justifyContent: "space-evenly",
marginBottom: 100,
},
priceBlock: {
alignItems: "center",
marginBottom: 10,
},
totalText: {
textAlign: "center",
fontSize:24,
fontWeight: "bold",
},
});
`
I have tried
parseDouble and toFixed(2)
To be honest I thought
let totalPrice = Object.keys(cartItem).map((menuKey) => (
totalPrice =+ cartItem[menuKey].cost))
would throw an error.
What I think is the best way to go about this is to use reduce to get your sum:
const initialValue = 0;
const totalPrice = Object.keys(cartItem).reduce((prevValue,currentVal)=>{
let num = parseFloat(cartItem[currentVal].cost);
// if number cant be parsed do nothing
if(isNaN(num))
return prevValue
return prevValue + num;
},initialValue);
If you want to use map:
let totalPrice = 0;
Object.keys(cartItem).map((menuKey) => {
let num = parseFloat(cartItem[menuKey].cost)
if(isNaN(num))
return
totalPrice += num
})
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 trying to change the state of my 'quantityCounter' but I'm getting an error as the title says. Can anyone help me with changing the state while the value also changes in the screen?
import React from 'react';
import { StyleSheet, View, Text, Button } from 'react-native';
import { useSelector } from 'react-redux';
import { selectCartItems } from '../../../../redux/reducer/cartReducer';
import { selectAllItems } from '../../../../redux/reducer/itemReducer';
const CartList = () => {
const cartItems = useSelector(selectCartItems);
const itemData = useSelector(selectAllItems);
const [quantityCounter, setQuantityCounter] = React.useState(cartItems);
function quantityHandler({id, num}){
const targetItem = itemData.find((item) => item.id === id);
let targetCart = quantityCounter.find((cart) => cart.id === id);
setQuantityCounter((previousState) =>
previousState.forEach(
(item) => {
if(item.id === id){
Object.keys(item).find(key => {
if(key === 'quantity'){
if(num === 1 && targetCart.quantity < targetItem.availableItem){
item[key] = targetCart.quantity + 1;
}
if(num === 0 && targetCart.quantity > 0) {
item[key] = targetCart.quantity - 1;
}
}
})
}
}));
}
return (
<>
{quantityCounter.map((item) => (
<View style={styles.CartItemsContainer} key={item.id}>
<Text style={styles.textStyle}>{item.productName}</Text>
<Text style={styles.textStyle}>{item.productPrice}</Text>
<View style={styles.quantityContainer}>
<Button title='-' onPress={() => quantityHandler({id : item.id, num: 0})}/>
<Text style={styles.quantityContainer__text}>{item.itemQuantity}</Text>
<Button title='+' onPress={() => quantityHandler({id : item.id, num: 1})}/>
</View>
</View>
))}
</>
)
}
const styles = StyleSheet.create({
CartItemsContainer:{
flexDirection: 'row', alignSelf: 'stretch'
},
textStyle: {
flex: 1, alignSelf: 'stretch',
borderWidth: 1, borderTopColor: 'transparent',
textAlign: 'center', textAlignVertical: 'center'
},
quantityContainer:{
flex: 1, alignSelf: 'stretch', flexDirection: 'row',
borderWidth: 1, borderTopColor: 'transparent',
alignItems: 'baseline', justifyContent: 'center'
},
quantityContainer__text:{
marginHorizontal: 5, marginVertical: 5
}
});
export default CartList;
Another approach I did was this but the state is only changing, in the screen it doesn't. When the 'quantityHandler' is being pressed, it works as what it is supposed to be but I don't know how to fix or make this work. I tried different way but I can't really show it. Please help.
import React from 'react';
import { StyleSheet, View, Text, Button } from 'react-native';
import { useSelector } from 'react-redux';
import { selectCartItems } from '../../../../redux/reducer/cartReducer';
import { selectAllItems } from '../../../../redux/reducer/itemReducer';
const CartList = () => {
const cartItems = useSelector(selectCartItems);
const itemData = useSelector(selectAllItems);
const [quantityCounter, setQuantityCounter] = React.useState(0);
let total = 0;
let id, quantity, name, price;
let cart_replica = [];
cartItems.forEach(item => {
id = item.id;
name = item.productName;
price = item.productPrice;
quantity = item.itemQuantity;
total += item.totalPrice;
cart_replica.push({id, name, quantity, price})
});
function quantityHandler({id, num}){
const targetItem = itemData.find((item) => item.id === id);
let targetCart = cart_replica.find((cart) => cart.id === id);
cart_replica.map(
(item) => {
if(item.id === id){
return { ...cart_replica, item: { ...item, quantity: item.quantity + 1}};
}
});
console.log(cart_replica[0])
}
return (
<>
{cart_replica.map((item) => (
<View style={styles.CartItemsContainer} key={item.id}>
<Text style={styles.textStyle}>{item.name}</Text>
<Text style={styles.textStyle}>{item.price}</Text>
<View style={styles.quantityContainer}>
<Button title='-' onPress={() => quantityHandler({id : item.id, num: 0})}/>
<Text style={styles.quantityContainer__text}>{item.quantity}</Text>
<Button title='+' onPress={() => quantityHandler({id : item.id, num: 1})}/>
</View>
</View>
))}
</>
)
}
const styles = StyleSheet.create({
CartItemsContainer:{
flexDirection: 'row', alignSelf: 'stretch'
},
textStyle: {
flex: 1, alignSelf: 'stretch',
borderWidth: 1, borderTopColor: 'transparent',
textAlign: 'center', textAlignVertical: 'center'
},
quantityContainer:{
flex: 1, alignSelf: 'stretch', flexDirection: 'row',
borderWidth: 1, borderTopColor: 'transparent',
alignItems: 'baseline', justifyContent: 'center'
},
quantityContainer__text:{
marginHorizontal: 5, marginVertical: 5
}
});
export default CartList;
Can you check this ,ive added state manipulation.
Hope it helps :)
https://snack.expo.dev/5vfUoenH3
import React from 'react';
import { StyleSheet, View, Text, Button, SafeAreaView } from 'react-native';
const App = () => {
const [quantityCounter, setQuantityCounter] = React.useState([
{
id: 1,
name: 'item 1',
availableItem: 5,
price: 500,
quantity: 5,
},
{
id: 2,
name: 'item 2',
availableItem: 4,
price: 500,
quantity: 4,
},
{
id: 3,
name: 'item 3',
availableItem: 3,
price: 500,
quantity: 3,
},
]);
const quantityHandler = (id,index,isIncrement) =>{
const newCopy = [...quantityCounter];
if(isIncrement){
newCopy[index].quantity = newCopy[index].quantity +1;
}else {
newCopy[index].quantity = newCopy[index].quantity -1;
}
console.log("er",newCopy,index)
setQuantityCounter(newCopy)
}
return (
<SafeAreaView style={styles.safeStyle}>
{quantityCounter.map((item,index) => (
<View style={styles.CartItemsContainer} key={item.id}>
<Text style={styles.textStyle}>{item.name}</Text>
<Text style={styles.textStyle}>{item.price}</Text>
<View style={styles.quantityContainer}>
<Button title='-' onPress={() => quantityHandler(item.id,index,false)}/>
<Text style={styles.quantityContainer__text}>{item.quantity}</Text>
<Button title='+' onPress={() => quantityHandler(item.id,index,true)}/>
</View>
</View>
))}
</SafeAreaView>
)
}
const styles = StyleSheet.create({
safeStyle: {
marginTop:'5%'
},
CartItemsContainer:{
flexDirection: 'row', alignSelf: 'stretch'
},
textStyle: {
flex: 1, alignSelf: 'stretch',
borderWidth: 1, borderTopColor: 'transparent',
textAlign: 'center', textAlignVertical: 'center'
},
quantityContainer:{
flex: 1, alignSelf: 'stretch', flexDirection: 'row',
borderWidth: 1, borderTopColor: 'transparent',
alignItems: 'baseline', justifyContent: 'center'
},
quantityContainer__text:{
marginHorizontal: 5, marginVertical: 5
}
});
export default App;
I'm trying to make animation in flatlist, something like this, it will have spring animation when moving item
Something like apple music you can see: https://streamable.com/yg1j2j
With the help of #David Scholz, I was able to make exactly the same, here is expo for that:
https://snack.expo.dev/#quockhanh210199/animated-list-selection
But problem is, when i enable scroll it will have weird animation, so my question is, how to make the animation behavior correct if i enable scroll, you can see the weird animation if you enable scroll and and more item, or use below code:
import React, {useState, useCallback} from 'react';
import { StyleSheet, Text,View, SafeAreaView, ScrollView, StatusBar, Animated,FlatList,TouchableOpacity } from 'react-native';
const App = () => {
const data = [
{
id: "1",
text: "Results",
},
{
id: "2",
text: "Products",
},
{
id: "3",
text: "Stores",
},
{
id: "4",
text: "Stores",
},
{
id: "5",
text: "Stores",
},
{
id: "6",
text: "Stores",
},
]
const [translateValue] = useState(new Animated.Value(0))
const [selected, setSelected] = useState(0)
const onPress = React.useCallback(
(index) => {
setSelected(index)
Animated.spring(translateValue, {
toValue: index * 100,
velocity: 5,
useNativeDriver: true,
}).start()
},
[translateValue, setSelected]
)
return (
<View style={{ flexDirection: "row", marginTop: 100 }}>
<Animated.View
style={[
{
position: "absolute",
backgroundColor: "black",
top: -5,
right: 0,
bottom: 0,
left: 15,
width: 70,
height: 30,
borderRadius: 12,
transform: [{ translateX: translateValue }],
},
]}
/>
<FlatList
data={data}
horizontal={true}
scrollEnabled={true}
renderItem={({ item, index }) => {
return (
<TouchableOpacity onPress={() => onPress(index)}>
<View style={{ width: 100, borderRadius: 10 }}>
<Text style={[{ textAlign: "center" }, index === selected ? { color: "white" } : { color: "black" }]}>
{item.text}
</Text>
</View>
</TouchableOpacity>
)
}}
keyExtractor={(item) => item.id}
/>
</View>
)
}
export default App
Please help, thank you so much
Below is the code component for the customer picker
import React, { useEffect, useState } from "react";
import { connect } from 'react-redux';
import {
TouchableOpacity,
FlatList,
SafeAreaView,
StatusBar,
StyleSheet,
Text,
View,
Button,
Alert,
} from "react-native";
import { screenHeight, screenWidth } from "../constants";
const DATA = [
{
id: "bd7acbea-c1b1-46c2-aed5-3ad53abb28ba",
title: "Client Not Found"
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d72",
title: "Client refused"
},
];
const Item = ({ item, onPress, style }) => (
<TouchableOpacity onPress={onPress} style={[styles.item, style]}>
<Text style={styles.title}>{item.title}</Text>
</TouchableOpacity>
);
const StatusOptions = (props) => {
const [selectedId, setSelectedId] = useState(null);
const renderSeparator = () => (
<View
style={{
backgroundColor: "grey",
height: 0.8
}}
/>
);
const ListHeader = () => {
//View to set in Header
return (
<View style={{ height: 20 }}></View>
);
};
const renderItem = ({ item }) => {
const backgroundColor = item.id === selectedId ? "#6cd9ff" : "white";
return (
<Item
item={item}
onPress={() => {
setSelectedId(item.id);
console.log("SELECTED ID _ STATUSOPTIONS component : ", selectedId);
const val = DATA.filter(status => status.id == selectedId).map(filteredStatus => filteredStatus.title);
console.log("VALLLLLLLLLLLLLLLLLLLLLLLUUUUUUEEEEEEEEEEEEEEEEEEEE:::: ", val);
props.navigation.navigate('AnotherScreen');
}}
style={{ backgroundColor }}
/>
);
};
return (
<View style={{ bottom: 0, flex: 1, position: 'absolute', width: screenWidth, }}>
<View style={styles.container}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
extraData={selectedId}
ItemSeparatorComponent={renderSeparator}
ListHeaderComponent={ListHeader}
style={{
backgroundColor: "white",
width: "100%",
borderTopRightRadius: 20,
borderTopLeftRadius: 20,
zIndex: 1,
}}
/>
</View>
<View style={{ backgroundColor: "grey", height: 0.4 }} />
<View style={styles.closeButtonContainer}>
<TouchableOpacity style={styles.closeButton}
onPress={() => {
props.setStatusOptionsVisible(false)
}}>
<Text style={styles.title}>Close</Text>
</TouchableOpacity>
</View>
</View>
);
};
function mapStateToProps(state) {
return {
StatusOptionsVisible: state.volunteerItems.statusOptionsVisible,
currentTaskItemId: state.taskItems.taskItemId,
};
}
function mapDispatchToProps(dispatch) {
return {
setStatusOptionsVisible: (visible) => dispatch({ type: 'SET_STATUS_VISIBLE', statusVisibility: visible }),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(StatusOptions);
const styles = StyleSheet.create({
closeButton: {
backgroundColor: 'lightgrey',
borderRadius: 10,
height: 50,
justifyContent: 'center',
width: '90%',
},
closeButtonContainer: {
alignItems: 'center',
height: 90,
justifyContent: 'center',
backgroundColor: 'white',
},
textStyle: {
textAlign: "center",
},
container: {
borderRadius: 30,
flex: 4,
width: screenWidth,
},
item: {
padding: 20
},
title: {
fontSize: 20,
fontWeight: 'bold',
color: "black",
textAlign: "center"
}
});
the console Log : ("SELECTED ID _ STATUSOPTIONS component : ", selectedId)
in render Item function returns null for first picker item selection and the returns the previous value for the next picker item selection , can anyone please help with fixing it ?
Try to use this
useEffect(() => {
console.log("SELECTED ID _ STATUSOPTIONS component : ", selectedId);
if(selectedId != null) {
const val = DATA.filter(status => status.id == selectedId).map(filteredStatus => filteredStatus.title);
console.log("VALLUUEEEEEEEEEEEEEEEEEEEE:::: ", val);
}
}, [selectedId])