Show different data based on the active carousel item

I have a horizontal <Animated.ScrollView>, a "carousel" in my React-native app that displays one item at the center of the screen and the edges of previous and next item. I want to show data (lessons) below the ScrollView. Can someone tell me or point to a resource about how can I know what item the screen is now displaying and then showing data based on that? Do I need to calculate the current item in the scrollview or pass it as an argument to some function?

My goal:

enter image description here

Parent component:

 return (
<View style={styles.screen}>
  <View style={styles.thumbnailScrollContainer}>
    <HorizontalContentScroll
      data={LESSONS_DATA}
    />
  </View>
  <View style={styles.dataScrollContainer}>
    <FlatList numColumns={2} data={lessonsByCategory} renderItem={renderLessonItem} />
  </View>
</View> );

And here my horizontal Scrollview

const HorizontalContentScroll = ({ data}: HorizontalContentProps) => {
  const { width, height } = Dimensions.get('window');
  const scrollX = useRef(new Animated.Value(0)).current;
  const ITEM_SIZE = width * 0.8;

  const getInterval = (offset: any) => {
    // console.log('offset', offset);
  };

  const scrollableData = (data as Array<ContentCategory>).map(
    (item: ContentCategory, index: number) => {
      const inputRange = [
        (index - 1) * ITEM_SIZE,
        index * ITEM_SIZE,
        (index + 1) * ITEM_SIZE,
      ];
      const translateY = scrollX.interpolate({
        inputRange,
        outputRange: [40, 10, 40],
        // extrapolate: 'clamp',
      });

      return (
        <Card
          size="large"
          style={{
            ...styles.titleCard,
            transform: [{ translateY }],
            width: ITEM_SIZE,
          }}
          key={`${item.category}-${index}`}
        >
          <Text>{item.category}</Text>
        </Card>
      );
    }
  );

  return (
    <Animated.ScrollView
      contentContainerStyle={styles.contentContainer}
      horizontal
      onScroll={Animated.event(
        [{ nativeEvent: { contentOffset: { x: scrollX } } }],
        {
          useNativeDriver: true,
          listener: (event) => {
            getInterval(event);
          },
        }
      )}
      scrollEventThrottle={16}
      showsHorizontalScrollIndicator={false}
      bounces={false}
      pagingEnabled
      snapToAlignment="center"
      snapToInterval={330}
      decelerationRate={'fast'}
    >
      {scrollableData}
    </Animated.ScrollView>
  );
};

export default HorizontalContentScroll;

I think I have to do something in this map function like pass the current item up to my parent component but how? If I try to call a function that sets the state in the parent I get an error of "Warning: Cannot update a component from inside the function body of a different component."

const scrollableData = (data as Array<ContentCategory>).map(
    (item: ContentCategory, index: number) => {
      const inputRange = [
        (index - 1) * ITEM_SIZE,
        index * ITEM_SIZE,
        (index + 1) * ITEM_SIZE,
      ];

      const translateY = scrollX.interpolate({
        inputRange,
        outputRange: [40, 10, 40],
      });

      // filterLessonsInTheParent(item)
      
      return (
        <Card
          size="large"
          style={{
            ...styles.titleCard,
            transform: [{ translateY }],
            width: ITEM_SIZE,
          }}
          key={`${item.category}-${index}`}
        >
          <Text>{item.category}</Text>
        </Card>
      );
    }

36 thoughts on “Show different data based on the active carousel item”

  1. Okay I solved it.

    I used Animated.Flatlist instead of Animated.Scrollview so that I could get my hands on the onViewableItemsChanged prop and then I had to refactor my component to a class component so that viewabilityConfig prop would work properly.

    I pass the current viewable item to the parent in a useCallback function that updates the local state. I then use that and React Pure Component to avoid re-rendering my HorizontalContentScroll which would mess up the animation positions. (I don’t know if this is the most optimal way but it works so far).

    // Parent
    const handleViewableChange = useCallback((item: ContentCategory) => {
        setContentsToShow((prevItem) => item.contents);
      }, []);
    
      return (
        <View style={styles.screen}>
          <View style={styles.thumbnailScrollContainer}>
            <HorizontalContentScroll
              data={LESSONS_DATA}
              onViewableChange={handleViewableChange }
            />
          </View>
          <View>
    
              <FlatList
                numColumns={2}
                data={contentsToShow}
                renderItem={renderLessonItem}
              /> 
    
    // HorizontalContentScroll
    class HorizontalContentScroll extends PureComponent<HoriProps, any> {
      viewabilityConfig: { viewAreaCoveragePercentThreshold: number };
      scrollX: any;
      constructor(props: any) {
        super(props);
    
        this.handleViewableItemsChanged = this.handleViewableItemsChanged.bind(
          this
        );
        this.viewabilityConfig = { viewAreaCoveragePercentThreshold: 50 };
      }
    
      handleViewableItemsChanged = (info: any) => {
        const currItemInView = info.viewableItems[0].item;
        this.props.onViewableChange(currItemInView);
      };
    
      render() {
        const { data } = this.props;
        const { width, height } = Dimensions.get('window');
        const scrollX = new Animated.Value(0);
        const ITEM_SIZE = width * 0.8;
    
        return (
          <Animated.FlatList
            data={data}
            contentContainerStyle={styles.contentContainer}
            horizontal
            pagingEnabled
            onViewableItemsChanged={this.handleViewableItemsChanged}
            viewabilityConfig={this.viewabilityConfig}
            scrollEventThrottle={16}
            showsHorizontalScrollIndicator={false}
            bounces={false}
            snapToAlignment="center"
            snapToInterval={330}
            decelerationRate={'fast'}
            onScroll={Animated.event(
              [{ nativeEvent: { contentOffset: { x: scrollX } } }],
              {
                useNativeDriver: true,
              }
            )}
            renderItem={({
              item,
              index,
            }: {
              item: ContentCategory;
              index: number;
            }) => {
              const inputRange = [
                (index - 1) * ITEM_SIZE,
                index * ITEM_SIZE,
                (index + 1) * ITEM_SIZE,
              ];
              const translateY = scrollX.interpolate({
                inputRange,
                // [x, y, x]
                outputRange: [40, 10, 40],
                // extrapolate: 'clamp',
              });
              return (
                <Card
                  size="large"
                  style={{
                    ...styles.titleCard,
                    transform: [{ translateY }],
                    width: ITEM_SIZE,
                  }}
                  key={`${item.category}-${index}`}
                >
                  <Text style={styles.categoryText}>{item.category}</Text>
                </Card>
              );
            }}
          />
        );
      }
    }
    
    export default HorizontalContentScroll;
    
    Reply

Leave a Comment