React Native Vertical Slider - react-native

I am Tring to make a screen in react native in which height of upper view is resizable via dragging from the bottom.enter image description here
Size of (1) increases on dragging (2)
So far I tried two approaches
1. PanResponder
const Chat = (props) => {
const orders = [
//Some Array
]
const Height = Dimensions.get('window').height
const boxHeight = useRef(new Animated.Value(Height / 3)).current
const panResponder = useRef(PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onPanResponderMove: (evt, gestureState) => {
const nextValue = boxHeight+gestureState.dy
let newVal = nextValue >= maxHeight?maxHeight:nextValue
newVal = newVal <= minHeight?minHeight:newVal
boxHeight.setValue(newVal)
},
}
)).current;
return (
<Container>
<Animated.View style={[{ borderBottomColor: '#ccc', borderBottomWidth: 3 }, { height:boxHeight }]}>
<FlatList
data={orders}
renderItem={({ item, index }) => <List item={item} index={index} />}
/>
<View {...panResponder.panHandlers} style={{backgroundColor:'#000',height:20}}></View >
</Animated.View>
<ChatScreen />
</Container>
)
}
Pan-gesture- handler
const Chat = (props) => {
const orders = [
//some array
]
const Height = Dimensions.get('window').height
const boxHeight = useRef(new Animated.Value(Height / 3)).current
const minHeight = 10
const maxHeight = Height - 100
const onPanGestureEvent = (event) => {
boxHeight.setValue(
Animated.diffClamp(
Animated.add(
orderBoxHeight, Animated.multiply(
event.nativeEvent.translationY, Animated.diffClamp(
event.nativeEvent.velocityY, 0, 1
)
)
)
), minHeight, maxHeight
)
}
return (
<Container>
<Animated.View style={[{ borderBottomColor: '#ccc', borderBottomWidth: 3 }, { height: boxHeight }]}>
<FlatList
data={orders}
renderItem={({ item, index }) => <OrderList item={item} index={index} />}
/>
<PanGestureHandler
onGestureEvent={(event) => onPanGestureEvent(event)}
>
<Animated.View style={{ backgroundColor: '#000', height: 20 }}></Animated.View >
</PanGestureHandler>
</Animated.View>
<ChatScreen />
</Container>
)
}
But none Worked... I have also tried few other variations of both but none of them worked as desired either it slides too fast or does not move at all

Related

How to get the active index of an item of a flatlist?

I'm trying to highlight an indicator based on the active item of a flatlist , How i'm trying to do it is like this :
const [activeIndex, setActiveIndex] = useState(0);
const windowWidth = useWindowDimensions().width;
const onFlatlistUpdate = useCallback(({ viewableItems }: any) => {
if (viewableItems.length > 0) {
setActiveIndex(viewableItems[0].index);
}
console.log(viewableItems);
}, []);
const renderItem: ListRenderItem<string> = ({ item }) => {
return (
<Image
style={[styles.image, { width: windowWidth - 40 }]}
source={{ uri: item }}
/>
);
};
return (
<View style={styles.root}>
<FlatList
keyExtractor={(key) => key}
decelerationRate="fast"
snapToInterval={windowWidth - 20}
snapToAlignment="center"
data={images}
renderItem={renderItem}
horizontal
showsHorizontalScrollIndicator={false}
viewabilityConfig={{
viewAreaCoveragePercentThreshold: 50,
}}
onViewableItemsChanged={onFlatlistUpdate}
/>
I'm using a callback so don't get the changing onviewableitems on the fly isn't supported but it seems like the
<View style={styles.dots}>
{images.map((image, index) => (
<View
style={[
styles.dot,
{
backgroundColor: index === activeIndex ? "#c9c9c9" : " #ededed",
},
]}
/>
))}
</View>
when i change the item on the flatlist the chosen item becomes highlighted and all previous highlighted items are also highlighted if that makes sense , so if i'm on the first image of the flatlist the first dots is highlighted
Render item provides an index prop to your component
const renderItem: ListRenderItem<string> = ({ item, index }) => {

React Native SVG Animation doesn't animate the last array item

I am new to React Native Animation & I was following a tutorial & I did exactly what he was teaching but I failed to get the desired result. I am trying to develop a multipage onboarding screen with a svg animation. The problem is that it works fine but the moment I reach the last item in my flatlist, it doesn't update the index and hence the animation is not completed for the last array item.
Below is the relevant code:
RegisterationScreen:
const data = [
<EmailComponent />,
<PasswordComponent />,
<DPComponent />,
<AgeComponent />,
];
const RegisterScreen = () => {
const scrollX = useRef(new Animated.Value(0)).current;
const [currentIndex, setCurrentIndex] = useState(0);
const slidesRef = useRef(null);
const viewableItemsChanged = useCallback(({viewableItems}) => {
console.log(viewableItems[0]);
setCurrentIndex(viewableItems[0].index);
}, []);
const scrollTo = () => {
if (currentIndex < data.length - 1) {
slidesRef.current.scrollToIndex({index: currentIndex + 1});
} else {
console.log('Last Item!');
}
};
const goBack = () => {
if (currentIndex === 0) {
return;
}
slidesRef.current.scrollToIndex({index: currentIndex - 1});
};
// console.log(currentIndex);
return (
<RegisterationStateProvider>
<View style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor="#ffffff" />
<SafeAreaView style={styles.back}>
<TouchableOpacity onPress={goBack} activeOpacity={0.6}>
<AntDesign name="arrowleft" size={32} color="black" />
</TouchableOpacity>
</SafeAreaView>
<View style={{flex: 3}}>
<FlatList
ref={slidesRef}
scrollEnabled
data={data}
keyExtractor={(_, index) => 'key' + index}
renderItem={({item}) => item}
horizontal
showsHorizontalScrollIndicator={false}
pagingEnabled
bounces={false}
onScroll={
Animated.event(
[{nativeEvent: {contentOffset: {x: scrollX}}}],
{useNativeDriver: false},
)}
scrollEventThrottle={32}
onViewableItemsChanged={viewableItemsChanged}
/>
</View>
<Dot data={data} scrollX={scrollX} />
<NextButton
scrollTo={scrollTo}
percentage={(currentIndex + 1) * (100 / data.length)}
/>
</View>
</RegisterationStateProvider>
);
};
NextButton:
const NextButton = ({percentage, scrollTo}) => {
const size = 100;
const strokeWidth = 4;
const center = size / 2;
const radius = size / 2 - strokeWidth / 2;
const circumference = 2 * Math.PI * radius;
// console.log(percentage);
const progressAnimation = useRef(new Animated.Value(0)).current;
const progressRef = useRef(null);
const animation = toValue => {
return Animated.timing(progressAnimation, {
toValue,
duration: 250,
useNativeDriver: false,
}).start();
};
useEffect(() => {
animation(percentage);
}, [percentage]);
useEffect(() => {
progressAnimation.addListener(
value => {
const strokeDashoffset =
circumference - (circumference * value.value) / 100;
if (progressRef?.current) {
progressRef.current.setNativeProps({
strokeDashoffset,
});
}
},
[percentage],
);
return () => {
progressAnimation.removeAllListeners();
};
}, []);
return (
<View style={styles.container}>
<Svg width={size} height={size}>
<G rotation="-90" origin={center}>
<Circle
stroke="#E6E7E8"
cx={center}
cy={center}
r={radius}
strokeWidth={strokeWidth}
/>
<Circle
ref={progressRef}
stroke="#FF5864"
cx={center}
cy={center}
r={radius}
strokeWidth={strokeWidth}
strokeDasharray={circumference}
strokeDashoffset={circumference}
/>
</G>
</Svg>
<TouchableOpacity
disabled={false}
onPress={scrollTo}
style={styles.button}
>
<AntDesign name="arrowright" size={35} color="#fff" />
</TouchableOpacity>
</View>
);
};
I cannot find the error/bug in the code, everything looks fine because when I check if (currentIndex < data.length - 1) in scrollTo function, it should render for index=3 because 3<4 is true obviously but unfortunately it doesn't work and no error in console either.
Below is the output of console for console.log(currentIndex); & console.log(percentage);
LOG {"index": 0, "isViewable": true, "item": <EmailComponent />, "key": "key0"}
LOG {"index": 0, "isViewable": true, "item": <EmailComponent />, "key": "key0"}
LOG {"index": 1, "isViewable": true, "item": <PasswordComponent />, "key": "key1"}
LOG {"index": 1, "isViewable": true, "item": <PasswordComponent />, "key": "key1"}
LOG {"index": 2, "isViewable": true, "item": <DPComponent />, "key": "key2"}
LOG 0
LOG 25
LOG 1
LOG 50
LOG 2
LOG 75
As you can see that even though I am at the last item, the animation is still at (75%) 270° & not full complete i.e. (100%) 360° & {index:3} is not logging in console even though I'm at last item of data.
onViewableItemsChanged is not being triggered on the last item in the FlatList. Lower the viewAreaCoveragePercentThreshold in the viewabilityConfig property https://reactnative.dev/docs/flatlist#viewabilityconfig

create Carousel in React Native using FlatList

I'm creating a carousel component in React Native using a FlatList and I use useState hook to control the index of image, images load properly and the problem is I cant use my buttons to control the carousel. for example when I tap on right arrow first time doesn't work but when I tap again it goes to next image.
here's my code:
const { width: windowWidth, height: windowHeight } = Dimensions.get("window");
const slideList = Array.from({ length: 5 }).map((_, i) => {
return {
id: i.toString(),
image: `https://picsum.photos/1440/2842?random=${i}`,
};
});
const Carousel = () => {
const [current, setCurrent] = useState(0);
const length = slideList.length;
const flatListRef = useRef();
const renderItem = ({ item }) => {
const arr = Object.values( item );
return (
<View style={styles.imagesContainer}>
<Image style={styles.image} source={{ uri: item.image }} />
</View>
);
}
const goNextSlide = () => {
setCurrent(current < length -1 ? current + 1 : 0);
flatListRef.current.scrollToIndex({ index: current, animated: true });
};
const goPrevSlide = () => {
setCurrent(current <= length - 1 && current >= 0 ? current -1 : 0);
flatListRef.current.scrollToIndex({ index: current, animated: true });
};
console.log(current)
return (
<View style={styles.screen}>
<View style={styles.controls}>
<TouchableOpacity style={styles.controlleft} onPress={goPrevSlide}>
<CarouselLeftArrow style={styles.leftArrow} size={28} fill='black' />
</TouchableOpacity>
<TouchableOpacity style={styles.controlRight} onPress={goNextSlide}>
<CarouselRightArrow style={styles.rightArrow} size={28} fill='black' />
</TouchableOpacity>
</View>
<FlatList
data={slideList}
keyExtractor={item => item.id}
renderItem={renderItem}
horizontal={true}
showsHorizontalScrollIndicator={false}
pagingEnabled={true}
ref={flatListRef}
/>
</View>
)
}
const styles = StyleSheet.create({
imagesContainer: {
width: windowWidth,
height: 250
},
image: {
width: '100%',
height: '100%'
},
controls: {
backgroundColor: 'yellow',
flexDirection: 'row',
justifyContent: 'space-between',
position: 'absolute',
zIndex: 2,
width: '100%',
top: 100
},
controlLeft: {
},
controlRight: {
}
})
export default Carousel;
any help would be appreciated.
goPrevSlide
setCurrent(current <= length - 1 && current >= 0 ? current -1 : 0);
When current >= 0 is not correct because if current equals zero then you set -1 to current in this case. Replace statement like setCurrent(current ? current - 1 : length - 1);
Since updating state is an async action, you can not handle updated variable immediately, you need to use effect hook in order to catch it.
useEffect(() => {
// fires every time when "current" is updated
flatListRef.current.scrollToIndex({ index: current, animated: true });
}, [current]);
Remove setCurrent function from both handler
try to give width and height to the images, you need that if source is uri.
see you code working at snack (without buttons)

React child prop doesn't change when parent state updates

I have a react native app and I want to update some states and pass a prop to other components. I am passing the focus state and setFocus to the "Search" component. The focus state on the "Vault" component updates properly but it doesn't affect the "Search" component. The code is like below. What am I doing wrong?
const Vault = ({ navigation }: VaultStackNavigationProps<"Vault">) => {
const [focus, setFocus] = useState(false);
React.useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<Right isFocus={focus}>
<Search handleFocus={setFocus} focus={focus} />
</Right>
),
});
}, [navigation]);
return (
<Box style={{ flex: 1, backgroundColor: "#1A1A1A" }}>
<Text color="white">hello</Text>
</Box>
);
};
export const Search = ({ handleFocus, focus }) => {
const [value, setValue] = useState("");
const inputRef = useRef<TextInput>();
const { width } = useWindowDimensions();
const onHandleFocus = (value) => {
handleFocus(value);
};
useEffect(() => {
if (focus) {
inputRef.current.focus();
} else {
Keyboard.dismiss();
clearTextState();
}
}, [focus]);
const clearTextState = () => {
setValue("");
};
const onClear = () => {
clearTextState();
inputRef.current.clear();
};
const onClose = () => {
onHandleFocus(false);
};
return (
<>
{focus && (
<TouchableOpacity onPress={onClose}>
<MaterialIcons name="arrow-back" size={24} color="white" />
</TouchableOpacity>
)}
{focus && (
<TextInput
ref={inputRef}
style={{
flex: 1,
color: "white",
paddingLeft: 15,
fontFamily: "CrimsonRegular",
}}
onChangeText={(text) => setValue(text)}
placeholder="Type here"
/>
)}
{focus && value.length > 0 && (
<SearchIconButton
onPress={onClear}
style={{ width: width / 9 }}
icon="close"
/>
)}
</>
);
};
Your layoutEffect is listening changes only from navigation
Try adding focus to useLayoutEffect array like
React.useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<Right isFocus={focus}>
<Search handleFocus={setFocus} focus={focus} />
</Right>
),
});
}, [navigation, focus]);

How to pass item id to Swipeable to delete or edit item with this id

Im using: react-native, expo,
react-native-gesture-handler/Swipeable
here is a code of screen https://github.com/ArturScwaiberov/react-native-dental-app/blob/master/screens/HomeScreen.js
In my app there is list of appointments rendered by SectionList.
In renderItem I created two buttons, one of them is to delete item.
So, I cant understand how should I pass appointment ID to renderRightActions to delete or edit this current appointment.. please help me find out the solution!
Here is my HomeScreen code review:
const HomeScreen = ({ navigation }) => {
const [data, setData] = useState(null)
const [refreshing, setRefreshing] = useState(false)
const fetchAppointments = () => {
setRefreshing(true)
appointmentsApi
.get()
.then(({ data }) => {
setData(data.message)
setRefreshing(false)
})
.catch((e) => {
setRefreshing(false)
console.log(e)
})
}
useEffect(fetchAppointments, [])
const removeAppointment = (id) => {
console.log(id)
const result = data.map((group) => {
group.data = group.data.filter((item) => item._id !== id)
return group
})
setData(result)
//appointmentsApi.remove(id)
}
renderRightAction = (text, color, x, progress) => {
const trans = progress.interpolate({
inputRange: [0, 1],
outputRange: [x, 0],
})
const pressHandler = () => {
if (text === 'pencil') {
alert('hey')
} else {
//but how to get over here the ID of item from SectionList?
removeAppointment(id)
}
}
return (
<Animated.View style={{ flex: 1, transform: [{ translateX: trans }] }}>
<RectButton
style={{
alignItems: 'center',
flex: 1,
justifyContent: 'center',
backgroundColor: color,
}}
onPress={pressHandler}
>
<ActionText>
<Octicons name={text} size={24} color='white' />
</ActionText>
</RectButton>
</Animated.View>
)
}
renderRightActions = (progress) => (
<RightButtonsHandler>
{renderRightAction('pencil', '#B4C1CB', 160, progress)}
{renderRightAction('trashcan', '#F85A5A', 80, progress)}
</RightButtonsHandler>
)
return (
<Container>
<SectionList
style={{ paddingLeft: 20, paddingRight: 20 }}
sections={data}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => (
<Swipeable renderRightActions={renderRightActions} friction={2}>
<Appointment navigation={navigation} item={item} />
</Swipeable>
)}
renderSectionHeader={({ section: { title } }) => <SectionTitle>{title}</SectionTitle>}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={fetchAppointments} />}
/>
<PluseButton
style={{
shadowColor: '#2A86FF',
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.3,
shadowRadius: 4.65,
elevation: 8,
}}
onPress={() => navigation.navigate('AddPatient')}
>
<Ionicons name='ios-add' size={32} color='white' />
</PluseButton>
</Container>
)
}
You only need to pass the item id as a function param.
renderRightActions={(progress) => renderRightActions(progress, item.id)}
I made all changes. Try this code:
const HomeScreen = ({ navigation }) => {
const [data, setData] = useState(null)
const [refreshing, setRefreshing] = useState(false)
const fetchAppointments = () => {
setRefreshing(true)
appointmentsApi
.get()
.then(({ data }) => {
setData(data.message)
setRefreshing(false)
})
.catch((e) => {
setRefreshing(false)
console.log(e)
})
}
useEffect(fetchAppointments, [])
const removeAppointment = (id) => {
console.log(id)
const result = data.map((group) => {
group.data = group.data.filter((item) => item._id !== id)
return group
})
setData(result)
//appointmentsApi.remove(id)
}
renderRightAction = (text, color, x, progress, id) => {
const trans = progress.interpolate({
inputRange: [0, 1],
outputRange: [x, 0],
})
const pressHandler = () => {
if (text === 'pencil') {
alert('hey')
} else {
// but how to get over here the ID of item from SectionList?
removeAppointment(id) // its simple! :)
}
}
return (
<Animated.View style={{ flex: 1, transform: [{ translateX: trans }] }}>
<RectButton
style={{
alignItems: 'center',
flex: 1,
justifyContent: 'center',
backgroundColor: color,
}}
onPress={pressHandler}
>
<ActionText>
<Octicons name={text} size={24} color='white' />
</ActionText>
</RectButton>
</Animated.View>
)
}
renderRightActions = (progress, id) => (
<RightButtonsHandler>
{renderRightAction('pencil', '#B4C1CB', 160, progress, id)}
{renderRightAction('trashcan', '#F85A5A', 80, progress, id)}
</RightButtonsHandler>
)
return (
<Container>
<SectionList
style={{ paddingLeft: 20, paddingRight: 20 }}
sections={data}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => (
<Swipeable renderRightActions={(progress) => renderRightActions(progress, item.id)} friction={2}>
<Appointment navigation={navigation} item={item} />
</Swipeable>
)}
renderSectionHeader={({ section: { title } }) => <SectionTitle>{title}</SectionTitle>}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={fetchAppointments} />}
/>
<PluseButton
style={{
shadowColor: '#2A86FF',
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.3,
shadowRadius: 4.65,
elevation: 8,
}}
onPress={() => navigation.navigate('AddPatient')}
>
<Ionicons name='ios-add' size={32} color='white' />
</PluseButton>
</Container>
)
}