Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to make an infinite image carousel with hooks in react native

I am making an infinite image carousel using hooks and javascript in react native.

I am almost done with the code and also the carousel works absolutely fine but the active dots below the carousel runs faster than the images. I have tried a couple of things but unfortunately it didn't worked out.

Thanks in advance.

import React, {
  useEffect,
  useState,
  useRef,
  useCallback,
  createRef,
} from 'react';
import {
  StyleSheet,
  View,
  Dimensions,
  FlatList,
  LayoutAnimation,
  UIManager,
  Text,
} from 'react-native';
import {ActivityIndicator} from 'react-native';
import {Image} from 'react-native-elements';

const HomeCarousel = ({data}) => {
  const [dimension, setDimension] = useState(Dimensions.get('window'));
  const [index, setIndex] = useState(0);
  const [dataState, setDataState] = useState(data);

  slider = createRef();
  let intervalId = null;

  const onChange = () => {
    setDimension(Dimensions.get('window'));
  };

  useEffect(() => {
    Dimensions.addEventListener('change', onChange);
    return () => {
      Dimensions.removeEventListener('change', onChange);
    };
  });

  useEffect(() => {
    if (Platform.OS === 'android') {
      UIManager.setLayoutAnimationEnabledExperimental(true);
    }
  }, []);

  viewabilityConfig = {
    viewAreaCoveragePercentThreshold: 50,
  };

  const onViewableItemsChanged = ({viewableItems, changed}) => {
    if (viewableItems.length > 0) {
      let currentIndex = viewableItems[0].index;
      if (currentIndex % data.length === data.length - 1) {
        setIndex(currentIndex),
          setDataState(dataState => [...dataState, ...data]);
      } else {
        console.log(currentIndex, 'else');
        setIndex(currentIndex);
      }
    }
  };

  const onSlideChange = () => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeIn);

    const newIndex = index + 1;
    setIndex(newIndex);

    slider?.current?.scrollToIndex({
      index: index,
      animated: true,
    });
  };

  const startInterval = useCallback(() => {
    intervalId = setInterval(onSlideChange, 3000);
  }, [onSlideChange]);

  useEffect(() => {
    startInterval();

    return () => {
      clearInterval(intervalId);
    };
  }, [onSlideChange]);

  const viewabilityConfigCallbackPairs = useRef([
    {viewabilityConfig, onViewableItemsChanged},
  ]);

  const renderIndicator = ()=>{
    const indicators = [];
    data.map((val,key)=>(
      indicators.push(
        <Text
        key={key}
        style={
          key === index % data.length ? {color: 'lightblue',fontSize:10,marginBottom: 8,marginHorizontal:1} :
          {color: '#888',fontSize:10,marginBottom: 8,marginHorizontal:1}
        }>
        ⬤
      </Text>
      )
    ));
    return indicators;
  }

  return (
    <View style={{width: dimension.width,height: 280, backgroundColor: '#fff'}}>
      <FlatList
        ref={slider}
        horizontal
        pagingEnabled
        snapToInterval={dimension?.width}
        decelerationRate="fast"
        bounces={false}
        showsHorizontalScrollIndicator={false}
        data={dataState}
        renderItem={({item, index}) => (
          <>
            <View>
              <Image
                source={{uri: `${item.url}`}}
                style={{
                  width: dimension?.width,
                  height: 250,
                  resizeMode: 'cover',
                }}
                PlaceholderContent={<ActivityIndicator />}
              />
            </View>
          </>
        )}
        viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current}
        getItemLayout={(data, index) => ({
          length: dimension?.width,
          offset: dimension?.width * index,
          index,
        })}
        windowSize={1}
        initialNumToRender={1}
        maxToRenderPerBatch={1}
        removeClippedSubviews={true}
      />
      <View
        style={{
          flexDirection: 'row',
          position: 'absolute',
          bottom: 0,
          alignSelf: 'center',
        }}>
        {renderIndicator()}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({});

export default HomeCarousel;


The data that is passed as the props to this component is

export 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'},
];
like image 816
shahbaaz ahmad Avatar asked Sep 07 '25 00:09

shahbaaz ahmad


1 Answers

Ooooooh, I fixed it myself Here is the perfectly working code full code. 😄

import React, {
  useEffect,
  useState,
  useRef,
  useCallback,
  createRef,
} from 'react';
import {
  StyleSheet,
  View,
  Dimensions,
  FlatList,
  LayoutAnimation,
  UIManager,
  Text,
} from 'react-native';
import {ActivityIndicator} from 'react-native';
import {Image} from 'react-native-elements';

const HomeCarousel = ({data}) => {
  const [dimension, setDimension] = useState(Dimensions.get('window'));
  const [index, setIndex] = useState(0);
  const [dataState, setDataState] = useState(data);
  const [indicatorIndex, setindicatorIndex] = useState();

  slider = createRef();
  let intervalId = null;

  const onChange = () => {
    setDimension(Dimensions.get('window'));
  };

  useEffect(() => {
    Dimensions.addEventListener('change', onChange);
    return () => {
      Dimensions.removeEventListener('change', onChange);
    };
  });

  useEffect(() => {
    if (Platform.OS === 'android') {
      UIManager.setLayoutAnimationEnabledExperimental(true);
    }
  }, []);

  viewabilityConfig = {
    viewAreaCoveragePercentThreshold: 50,
  };

  const onViewableItemsChanged = ({viewableItems, changed}) => {
    if (viewableItems.length > 0) {
      let currentIndex = viewableItems[0].index;
      if (currentIndex % data.length === data.length - 1) {
        setIndex(currentIndex), setindicatorIndex(currentIndex);
        setDataState(dataState => [...dataState, ...data]);
      } else {
        console.log(currentIndex, 'else');
        setIndex(currentIndex);
        setindicatorIndex(currentIndex);
      }
    }
  };

  const onSlideChange = () => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeIn);

    const newIndex = index + 1;
    setIndex(newIndex);

    slider?.current?.scrollToIndex({
      index: index,
      animated: true,
    });
  };

  const startInterval = useCallback(() => {
    intervalId = setInterval(onSlideChange, 3000);
  }, [onSlideChange]);

  useEffect(() => {
    startInterval();

    return () => {
      clearInterval(intervalId);
    };
  }, [onSlideChange]);

  const viewabilityConfigCallbackPairs = useRef([
    {viewabilityConfig, onViewableItemsChanged},
  ]);

  const renderIndicator = () => {
    const indicators = [];
    data.map((val, key) =>
      indicators.push(
        <Text
          key={key}
          style={
            key === indicatorIndex % data.length
              ? {
                  color: 'lightblue',
                  fontSize: 10,
                  marginBottom: 8,
                  marginHorizontal: 1,
                }
              : {
                  color: '#888',
                  fontSize: 10,
                  marginBottom: 8,
                  marginHorizontal: 1,
                }
          }>
          ⬤
        </Text>,
      ),
    );
    return indicators;
  };

  return (
    <View
      style={{width: dimension.width, height: 280, backgroundColor: '#fff'}}>
      <FlatList
        ref={slider}
        horizontal
        pagingEnabled
        snapToInterval={dimension?.width}
        decelerationRate="fast"
        bounces={false}
        showsHorizontalScrollIndicator={false}
        data={dataState}
        renderItem={({item, index}) => (
          <>
            <View>
              <Image
                source={{uri: `${item.url}`}}
                style={{
                  width: dimension?.width,
                  height: 250,
                  resizeMode: 'cover',
                }}
                PlaceholderContent={<ActivityIndicator />}
              />
            </View>
          </>
        )}
        viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current}
        getItemLayout={(data, index) => ({
          length: dimension?.width,
          offset: dimension?.width * index,
          index,
        })}
        windowSize={1}
        initialNumToRender={1}
        maxToRenderPerBatch={1}
        removeClippedSubviews={true}
      />
      <View
        style={{
          flexDirection: 'row',
          position: 'absolute',
          bottom: 0,
          alignSelf: 'center',
        }}>
        {renderIndicator()}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({});

export default HomeCarousel;

like image 153
shahbaaz ahmad Avatar answered Sep 08 '25 23:09

shahbaaz ahmad