Creating an auto-scrolling 小arousel component using FlashList in React Native
Sasha Kovalchuk
Posted on April 5, 2024
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
/>
)
}
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}
/>
)
}
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}
/>
)
}
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!
Posted on April 5, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
April 5, 2024