David Fox
David Fox

Reputation: 105

How do I build a multi-card carousel?

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:

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

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

Upvotes: 1

Views: 1849

Answers (3)

Ovidiu Cristescu
Ovidiu Cristescu

Reputation: 1053

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)

Upvotes: 0

David Scholz
David Scholz

Reputation: 9856

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.

Upvotes: 1

Nensi Kardani
Nensi Kardani

Reputation: 2366

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!

Upvotes: 0

Related Questions