I’m having a strange behavior where the Slider I made sometimes returns to index 0 (first slide), but most times it returns to index 1. I made a Sandbox in case you need it, but I can’t figure out how to make expo work in sandbox.
HERE is the sandbox
Here’s the code for that view
import React, { useEffect, useRef, useState } from "react";
import { StyleSheet, SafeAreaView, Animated, FlatList, View, Text, Image, Dimensions, Pressable } from "react-native";
const data = [
{
id: 1,
text: "Making Testing Easier",
img: "https://e1.pxfuel.com/desktop-wallpaper/85/884/desktop-wallpaper-beautiful-judo-throws-judoka-thumbnail.jpg",
},
{
id: 2,
text: "Instructional Videos",
img: "https://i.pinimg.com/originals/2b/84/c9/2b84c904ec3373b90f4fcad956233211.jpg",
},
{
id: 3,
text: "Newaza Fully Expanded",
img: "https://i.pinimg.com/474x/d4/55/8c/d4558cf325344900b9eead6cb74dab20.jpg",
},
];
const { width } = Dimensions.get("screen");
const CustomButton = ({ onPress, title, btnstyle, textstyle }) => {
return (
<Pressable style={btnstyle} onPress={onPress}>
<Text style={textstyle}>{title}</Text>
</Pressable>
);
};
const SlideItem = ({ item }) => {
return (
<View style={styles.slideitemcontainer}>
<View style={styles.overlay}>
<Image source={item.img} resizeMode="cover" style={styles.image} />
</View>
<View style={styles.txtcontent}>
<Text style={styles.title}>{item.text}</Text>
</View>
<View style={styles.btncontent}>
<CustomButton
title="Get Started"
btnstyle={styles.btnPrimary}
textstyle={styles.btnPrimaryText}
/>
<CustomButton
title="Login"
btnstyle={styles.btnSecondary}
textstyle={styles.btnSecondaryText}
/>
</View>
</View>
);
};
const Pagination = ({ data, scrollX }) => {
return (
<View style={styles.paginationcontainer}>
{data.map((_, idx) => {
const inputRange = [(idx - 1) * width, idx * width, (idx + 1) * width];
const dotWidth = scrollX.interpolate({
inputRange,
outputRange: [12, 30, 12],
extrapolate: "clamp",
});
const backgroundColor = scrollX.interpolate({
inputRange,
outputRange: [
"rgba(255, 255, 255, .3)",
"rgba(204, 204, 204, .7)",
"rgba(255, 255, 255, .3)",
],
extrapolate: "clamp",
});
return (
<Animated.View
key={idx.toString()}
style={[styles.dot, { width: dotWidth, backgroundColor }]}
/>
);
})}
</View>
);
};
const App = () => {
const [index, setIndex] = useState(0);
const scrollX = useRef(new Animated.Value(0)).current;
const flatlistRef = useRef(null);
const handleScroll = (event) => {
Animated.event(
[
{
nativeEvent: {
contentOffset: {
x: scrollX,
},
},
},
],
{
useNativeDriver: false,
},
)(event);
};
const handleOnViewableItemsChanged = useRef(({ viewableItems }) => {
setIndex(viewableItems[0].index);
}).current;
const viewabilityConfig = useRef({
itemVisiblePercentThreshold: 50,
}).current;
useEffect(() => {
flatlistRef?.current?.scrollToIndex({
behavior: "smooth",
animated: true,
index: index,
});
}, [index]);
useEffect(() => {
const timer = setTimeout(() => {
const nextIndex = index === data.length - 1 ? 0 : index + 1;
setIndex(nextIndex);
}, 2000);
return () => clearTimeout(timer);
}, [index]);
const getItemLayout = (data, index) => ({
length: width,
offset: width * index,
index,
});
return (
<SafeAreaView style={styles.container}>
<FlatList
data={data}
ref={flatlistRef}
getItemLayout={getItemLayout}
renderItem={({ item }) => <SlideItem item={item} />}
horizontal
pagingEnabled
snapToAlignment="center"
showsHorizontalScrollIndicator={false}
onScroll={handleScroll}
onViewableItemsChanged={handleOnViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
/>
<Pagination data={data} scrollX={scrollX} index={index} />
</SafeAreaView>
);
};
export default App;
const styles = StyleSheet.create({
overlay: {
position: "absolute",
width: "100%",
height: "100%",
flex: 1,
left: 0,
top: 0,
opacity: 0.7,
},
slideitemcontainer: {
width,
height,
alignItems: "center",
backgroundColor: "black",
},
image: {
flex: 1,
width,
},
txtcontent: {
position: "absolute",
top: "25%",
paddingHorizontal: "2%",
},
btncontent: {
position: "absolute",
bottom: "25%",
width: "70%",
},
title: {
fontSize: 45,
fontWeight: "bold",
color: "#fff",
},
btnPrimary: {
alignItems: "center",
justifyContent: "center",
paddingVertical: 8,
paddingHorizontal: 36,
borderRadius: 8,
elevation: 3,
backgroundColor: "#0079D1",
borderColor: "#0079D1",
borderStyle: "solid",
borderWidth: 2,
marginBottom: 10,
},
btnPrimaryText: {
fontSize: 13,
fontWeight: "600",
color: "white",
textTransform: "uppercase",
},
btnSecondary: {
alignItems: "center",
justifyContent: "center",
paddingVertical: 8,
paddingHorizontal: 36,
borderRadius: 8,
elevation: 3,
backgroundColor: "white",
borderColor: "#0079D1",
borderStyle: "solid",
borderWidth: 2,
},
btnSecondaryText: {
fontSize: 13,
fontWeight: "600",
color: "#0079D1",
textTransform: "uppercase",
},
usjacontainer: {
width,
display: "flex",
alignItems: "center",
position: "absolute",
bottom: "13%",
},
usja: {
width: 40,
height: 40,
alignItems: "center",
display: "flex",
justifyContent: "center",
},
paginationcontainer: {
position: "absolute",
bottom: 35,
flexDirection: "row",
height: 64,
width: "100%",
justifyContent: "center",
alignItems: "center",
},
dot: {
height: 12,
width: 12,
borderRadius: 6,
marginHorizontal: 3,
backgroundColor: "rgba(255, 255, 255, .4)",
},
dotActive: {
backgroundColor: theme.colors.secondary,
},
});
More specifically, the useEffect is probably where I am messing up
useEffect(() => {
const timer = setTimeout(() => {
const nextIndex = index === data.length - 1 ? 0 : index + 1;
setIndex(nextIndex);
}, 2000);
return () => clearTimeout(timer);
}, [index]);
SIDENOTE – Is there a way to make it so that the animation only moves forward, rather than a backwards slide to 0? Infinity scroll is always so much more pleasant.