DEV Community

Cover image for Creating an auto-scrolling Сarousel component using FlashList in React Native
Sasha Kovalchuk
Sasha Kovalchuk

Posted on

Creating an auto-scrolling Сarousel component using FlashList in React Native

We all love FlashList, it is a great and modern replacement for FlatList. Like FlatList, FlashList supports horizontal scrolling and we can quite easily use it as a carousel. The purpose of my article is to show exactly what can be done and add automatic scrolling of carousel components

Creating the Carousel Component

First, let's make a Carousel component using FlashList from Shopify. For simplicity of the example, the Props of our component will correspond to FlashListProps

Here's a setup:

import React, { useRef } from 'react'
import { FlashList, FlashListProps } from '@shopify/flash-list'

export const Carousel = (props: FlashListProps) => {
  const carouselRef = useRef<FlashList>(null)

  return (
    <FlashList
      ref={carouselRef}
      {...props}
      showsHorizontalScrollIndicator={false}
      decelerationRate={'fast'}
      pagingEnabled
      horizontal
    />
  )
}
Enter fullscreen mode Exit fullscreen mode

Adding Auto Scrolling

Now let's make the Carousel autoscrollable. In our example, the carousel will stop at the end, but you can easily change the code and return it to the first element if needed

Look at this part:

...
const AUTO_SCROLL_INTERVAL = 5000

export const Carousel = (props: FlashListProps) => {
  ...
  const autoScrollTimerRef = useRef<ReturnType<typeof setTimeout>>()
  const [autoScroll, setAutoScroll] = useState(true)
  const [visibleItemIndex, setVisibleItemIndex] = useState(0)

  const handleViewableItemsChanged = useCallback(
    ({ viewableItems }: { viewableItems: ViewToken[] }) => {
      const firstVisibleItemIndex = viewableItems?.[0]?.index
      const lastDataIndex = props.data.length - 1

      // remove this if your carousel should not stop
      if (firstVisibleItemIndex === lastDataIndex) {
        setAutoScroll(false)
      }

      if (typeof firstVisibleItemIndex === 'number') {
        setVisibleItemIndex(firstVisibleItemIndex)
      }
    },
    [props.data]
  )

  useEffect(() => {
    if (autoScroll) {
      const nextItemIndex = visibleItemIndex + 1
      const hasNextItem = !!props.data?.[nextItemIndex]

      if (hasNextItem) {
        autoScrollTimerRef.current = setTimeout(() => {
          carouselRef.current?.scrollToIndex({
            index: nextItemIndex,
            animated: true,
          })
        }, AUTO_SCROLL_INTERVAL)
      } else {
        // here you can return the carousel to the beginning if needed
      }
    }
  }, [autoScroll, props.data, visibleItemIndex])

  return (
    <FlashList
      ref={carouselRef}
      {...props}
      ...
      onViewableItemsChanged={handleViewableItemsChanged}
    />
  )
}
Enter fullscreen mode Exit fullscreen mode

Why do we need handleViewableItemsChanged? So we will always know in the code which item the user sees at the moment. Don't forget that the user can scroll our carousel manually, see the next section on how to control this

User Interaction with auto scrolling

Users might want to look longer at an item. When they touch the Carousel, we should stop auto-scrolling. When they stop touching, it starts moving again. We'll even add a delay after which the carousel will start scrolling again on its own

Here's how:

...
const AUTO_SCROLL_PAUSE = 5000

export const Carousel = (props: FlashListProps) => {
  ...
  const userTouchTimerRef = useRef<ReturnType<typeof setTimeout>>()

  const handleUserAnyTouch = useCallback(() => {
    clearTimeout(autoScrollTimerRef.current)
    clearTimeout(userTouchTimerRef.current)
    setAutoScroll(false)

    userTouchTimerRef.current = setTimeout(() => {
      setAutoScroll(true)
    }, AUTO_SCROLL_PAUSE)
  }, [])

  return (
    <FlashList
      ref={carouselRef}
      {...props}
      ...
      onTouchStart={handleUserAnyTouch}
    />
  )
}
Enter fullscreen mode Exit fullscreen mode

Conclusions

I tried to show that making such an important interactive element as Carousel is not difficult. You can use it as a starting point

You can see the final code as a ready-to-use library here:

Some optimizations have already been added there in the form of memoization of necessary pieces of code, clearing timers when the component is destroyed, and others. You can use this library in your own projects or use the developments we have discussed here to make your own

In any case, I will be glad to receive your stars on github, comments and questions here. Cheers!

Top comments (0)