'how can I make a custom carousel or image slide in react native without using any npm packages
I am making a custom image carousel or an image slider in react native without using any packages, the issue is that the dot indicators on the bottom are changing fine after 3 second intervals but the image doesn't slides automatically. There is some minor issue which I am unable to find. If you can help in getting this fixed without changing much of the code it would be really nice. thanks in advance.
Here is the full component code.
import React, {useEffect, useState, useRef} from 'react';
import {StyleSheet, ScrollView, View, Dimensions, Text} from 'react-native';
import {ActivityIndicator} from 'react-native';
import {Image} from 'react-native-elements';
const HomeCarousel = () => {
const [dimension, setDimension] = useState(Dimensions.get('window'));
const [selectedIndex, setSelectedIndex] = useState(0);
const scrollRef = useRef();
const onChange = () => {
setDimension(Dimensions.get('window'));
};
useEffect(() => {
Dimensions.addEventListener('change', onChange);
return () => {
Dimensions.removeEventListener('change', onChange);
};
});
useEffect(() => {
setInterval(() => {
setSelectedIndex(prevSelectedIndex =>
prevSelectedIndex === carouselImages.length - 1
? 0
: prevSelectedIndex + 1,
);
() => {
scrollRef.current.scrollTo({
animated: true,
y: 0,
x: dimension.width * selectedIndex,
});
};
}, 3000);
}, []);
const carouselImages = [
{url: 'https://i.ibb.co/FDwNR9d/img1.jpg'},
{url: 'https://i.ibb.co/7G5qqGY/1.jpg'},
{url: 'https://i.ibb.co/Jx7xqf4/pexels-august-de-richelieu-4427816.jpg'},
{url: 'https://i.ibb.co/GV08J9f/pexels-pixabay-267202.jpg'},
{url: 'https://i.ibb.co/sK92ZhC/pexels-karolina-grabowska-4210860.jpg'},
];
const setIndex = event => {
let viewSize = event.nativeEvent.layoutMeasurement.width;
let contentOffset = event.nativeEvent.contentOffset.x;
let carouselIndex = Math.floor(contentOffset / viewSize);
setSelectedIndex(carouselIndex);
};
return (
<View style={{width: dimension.width}}>
<ScrollView
horizontal
ref={scrollRef}
onMomentumScrollEnd={setIndex}
showsHorizontalScrollIndicator={false}
pagingEnabled>
{carouselImages.map((value, key) => (
<Image
source={{uri: `${value.url}`}}
style={{width: dimension?.width, height: 250, resizeMode: 'cover'}}
PlaceholderContent={<ActivityIndicator />}
/>
))}
</ScrollView>
<View
style={{
flexDirection: 'row',
position: 'absolute',
bottom: 0,
alignSelf: 'center',
}}>
{carouselImages.map((val, key) => (
<Text
key={key}
style={key === selectedIndex ? {color: 'white'} : {color: '#888'}}>
⬤
</Text>
))}
</View>
</View>
);
};
const styles = StyleSheet.create({});
export default HomeCarousel;
Solution 1:[1]
Your images aren't being update because of 2 things:
- You're setting the update in an anonymous function which isn't being called:
// You're just defining the function here and not actually calling it
() => {
scrollRef.current.scrollTo({
animated: true,
y: 0,
x: dimension.width * selectedIndex,
});
};
- Your
useEffectdoesn't have any dependencies, which means its callback won't be changed, and therefore theselectedIndexinside of it will always be 0:
useEffect(() => {
setInterval(() => {
setSelectedIndex(prevSelectedIndex =>
prevSelectedIndex === carouselImages.length - 1
? 0
: prevSelectedIndex + 1,
);
() => {
scrollRef.current.scrollTo({
animated: true,
y: 0,
// Since there are no dependencies, this callback won't ever be changed and selectedIndex will always be 0
x: dimension.width * selectedIndex,
});
};
}, 3000);
// You have no dependencies here
}, []);
My suggestions for improvements/fixes would be:
- Extract the function to update the slides out of
useEffectcallback and apply auseCallbackhook withselectedIndexdependency. You should also calculate the value of the new index outside of thesetSelectedIndexhook, becausesetState/useStatedoesn't update immediately:
// You should also use an intervalId so that you can clear the interval/reset when needed
let intervalId = null;
const HomeCarousel = () => {
// ... rest of the HomeCarousel component
const onSlideChange = useCallback(() => {
// Calculate newIndex here and use it to update your state and to scroll to the new slide
const newIndex = selectedIndex === carouselImages.length - 1 ? 0 : selectedIndex + 1;
setSelectedIndex(newIndex);
scrollRef?.current?.scrollTo({
animated: true,
y: 0,
x: dimension.width * newIndex,
});
}, [selectedIndex]);
const startInterval = useCallback(() => {
intervalId = setInterval(onSlideChange, 3000);
}, [onSlideChange]);
useEffect(() => {
startInterval();
return () => {
// Clear the interval when component unmounts, otherwise you could have memory leaks
clearInterval(intervalId);
};
}, [onSlideChange]);
// ... rest of the HomeCarousel component
};
- Another thing I would suggest you to make this carousel complete is to clear the interval/stop the automatic sliding as soon as the user starts sliding, otherwise it would cause weird behavior and slides getting out of control. You can restart the automatic sliding as soon as the user has finished his sliding action:
const onTouchStart = () => {
// As soon as the user touches the slide, stop the automatic sliding
clearInterval(intervalId);
};
const onTouchEnd = () => {
// As soon as the user stops touching the slide, releases it, start the automatic sliding again
startInterval();
};
return (
<ScrollView
// ... Other scrollview props
onTouchStart={onTouchStart}
onTouchEnd={onTouchEnd}>
{/* ... Rest of the component */}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Kapobajza |
