skip to Main Content

I’m trying to build something like this in React Native. It will stretch across the whole page and will loop infinitely, there will be a ‘next’ and ‘previous’ button.

I’m new to React Native (coming from React), so am a little unsure about how to implement it.

I found this guide on YouTube helpful to get something very basic up and running.

Here is the code I have so far:

import React, {useCallback, useEffect, useRef, useState} from 'react';
import {withTheme} from 'react-native-paper';
import {
  View,
  StyleSheet,
  Text,
  Dimensions,
  Image,
  FlatList,
  Pressable,
} from 'react-native';
import PrismicText from '../prismicText';

const {width: windowWidth, height: windowHeight} = Dimensions.get('window');

const Slide = ({data}) => {
  return (
    <View
      style={{
        height: 400,
        width: 300,
        justifyContent: 'center',
        alignItems: 'center',
        marginRight: 15,
      }}>
      <Image
        source={{uri: data.image}}
        style={{width: '100%', height: '100%', borderRadius: 16}}></Image>
    </View>
  );
};

const Carousel = ({slice, theme}) => {
  const slideList = slice.items.map((item, index) => {
    return {
      id: index,
      image: item.image.url,
    };
  });

  const {colors, isTabletOrMobileDevice} = theme;
  const styles = isTabletOrMobileDevice ? mobileStyles : desktopStyles;

  const flatListRef = useRef(null);
  const viewConfig = {viewAreaCoveragePercentThreshold: 50};
  const [activeIndex, setActiveIndex] = useState(4);

  const onViewRef = useRef(({changed}) => {
    if (changed[0].isViewable) {
      setActiveIndex(changed[0].index);
    }
  });

  const handlePressLeft = () => {
    if (activeIndex === 0)
      return flatListRef.current?.scrollToIndex({
        animated: true,
        index: slideList.length - 1,
      });

    flatListRef.current?.scrollToIndex({
      index: activeIndex - 1,
    });
  };

  const handlePressRight = () => {
    if (activeIndex === slideList.length - 1)
      return flatListRef.current?.scrollToIndex({
        animated: true,
        index: 0,
      });

    flatListRef.current?.scrollToIndex({
      index: activeIndex + 1,
    });
  };

  return (
    <>
      <View
        style={{
          display: 'flex',
          flexDirection: 'row',
          justifyContent: 'space-between',
          paddingHorizontal: 16,
          paddingVertical: 8,
        }}>
        <Pressable style={[styles.chevron]} onPress={handlePressLeft}>
          Left
        </Pressable>
        <Pressable style={[styles.chevron]} onPress={handlePressRight}>
          Right
        </Pressable>
      </View>
      <FlatList
        ref={ref => (flatListRef.current = ref)}
        data={slideList}
        horizontal
        showsHorizontalScrollIndicator={false}
        snapToAlignment="center"
        pagingEnabled
        viewabilityConfig={viewConfig}
        onViewableItemsChanged={onViewRef.current}
        renderItem={({item}, i) => <Slide data={item} />}
        keyExtractor={item => item}
      />

      <View style={styles.index}>
        <Text category={'c2'} style={styles.indexText}>
          {activeIndex + 1} of {slideList.length} photos
        </Text>
      </View>
    </>
  );
};

const mobileStyles = StyleSheet.create({});

const desktopStyles = StyleSheet.create({});

export default withTheme(Carousel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

The problems I’m experiencing with this code:

  • I am setting the initial state of the active index to 4, but active index always starts at 0
  • Clicking the ‘right’ button doesn’t seem to change the active index
  • Clicking the ‘right’ button will move the carousel along by 1 increment, but it won’t go any further (even on smaller viewports like mobile where you can only see 1.5 cards, so it should move along many times to be able to see all of the cards)
  • Clicking the ‘left’ button seems to have the same issues as above
  • There is no infinite loop of the slides

My feeling is that there are two issues to be addressed:

  • Active index is broken and needs to be fixed
  • Modifications required to make the number of cards on the viewport responsive

I’ve spent a lot of time looking at this and can’t seem to figure it out. Any help would be much appreciated.

3

Answers


  1. You can use the below third-party library to achieve the above one quickly.

    react-native-snap-carousel

    You can check all the examples and use them according to your requirement.

    Hope it will help you!

    Login or Signup to reply.
  2. The first issue is easy to fix. You are expecting that the FlatList scrolls initially to the initial activeIndex, but you are not telling the FlatList to do so. There is a prop called initialScrollIndex that is designed for this purpose.

     <FlatList
        initialScrollIndex={4}
        ...
    

    The second issue is caused by a faulty implementation of the functions handlePressLeft and handlePressRight as well as providing

      const onViewRef = useRef(({changed}) => {
        if (changed[0].isViewable) {
          setActiveIndex(changed[0].index);
        }
      });
    

    I have removed the above completely.

    I have changed the activeIndex state to the following.

    const [activeIndex, setActiveIndex] = useState({index: 4, direction: 'right'});
    

    I have changed the handlePressLeft and handlePressRight functions to the following.

    const handlePressLeft = () => {
      setActiveIndex((prev) => ({index: prev.index - 1, direction: 'left'}));
    };
    
    const handlePressRight = () => {
      setActiveIndex((prev) => ({index: prev.index + 1, direction: 'right'}));
    };
    

    I have created an effect as follows.

    React.useEffect(() => {
      if (activeIndex.index === slideList.length - 1 && activeIndex.direction === 'right') {
        setActiveIndex({index: 0, direction: 'right'});
      } else if (activeIndex.index < 0 && activeIndex.direction === 'left') {
        setActiveIndex({index: slideList.length - 2, direction: 'left'})
      } else {
        flatListRef.current?.scrollToIndex({
          animated: true,
          index: activeIndex.index,
        });
      }
    }, [activeIndex, slideList.length]);
    
    

    I have implemented an adapted snack without images and using a dummy array.

    Login or Signup to reply.
  3. You should try react-native-reanimated-carousel.

    Why?

    • highly customizable + easy and fast to implement any carousel
    • It’s new and it uses react-native-reanimated for better performance (by running animations on the UI thread, rather than on JS thread)
    • solves all the issues that react-native-snap-carousel has (which is deprecated and has lots of bugs)
    • solves all the issues that you have and handles many edge cases that you may have forgotten about (in case you want to implement it by yourself)
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search