To detect "snap" in FlatList, you can use viewabilityConfig and onViewableItemsChanged Flatlist properties.
See code (for Class and Hooks Components) below where I handle "snap" in onViewChanged method:
import React, {useRef, useState} from 'react';
import {Dimensions, FlatList, View} from 'react-native';
// CLASS COMPONENT API
export class DemoViewabilityConfigClass extends React.Component {
flatlistRef;
constructor(props) {
super(props);
this.state = {
activeIndex: 0,
};
}
onViewChanged = ({viewableItems, index}) => {
console.log('I snapped to', viewableItems);
if (viewableItems.length > 0) {
const {item: activeItem, index: activeIndex} = viewableItems[0];
console.log(activeItem, activeIndex);
this.setState({activeIndex: activeIndex});
}
};
render() {
return (
<FlatList
ref={ref => (this.flatlistRef = ref)}
showsHorizontalScrollIndicator={false}
contentContainerStyle={{
marginHorizontal: 10,
}}
snapToInterval={Dimensions.get('window').width}
decelerationRate={0}
overScrollMode={'never'}
snapToAlignment={'center'}
data={[
{
id: 'something',
other: 'data',
},
{
id: 'else',
other: 'data',
},
]}
onScrollEndDrag={() => console.log('Scroll end')}
horizontal={true}
viewabilityConfig={{itemVisiblePercentThreshold: 70}}
onViewableItemsChanged={this.onViewChanged}
onScrollToIndexFailed={info => {
console.log('Scroll failed', info);
const wait = new Promise(resolve => setTimeout(resolve, 500));
wait.then(() => {
this.flatlistRef.current?.scrollToIndex({
index: info.index,
animated: true,
});
});
}}
renderItem={({item, index}) => {
return (
<View
style={{
marginRight: 20,
height: 20,
width: Dimensions.get('window').width - 20,
backgroundColor: 'red',
}}
/>
);
}}
/>
);
}
}
// HOOKS API
export function DemoViewabilityConfigHooks() {
const flatlistRef = useRef(null);
const [activeEventIndex, setActiveEventIndex] = useState(0);
const onViewChanged = React.useRef(({viewableItems}) => {
console.log('Items ', viewableItems);
if (viewableItems.length > 0) {
const {index, item} = viewableItems[0];
console.log('Active index', index);
setActiveEventIndex(index);
}
});
const viewConfigRef = React.useRef({viewAreaCoveragePercentThreshold: 50});
return (
<FlatList
ref={flatlistRef}
showsHorizontalScrollIndicator={false}
contentContainerStyle={{
marginHorizontal: 10,
}}
snapToInterval={Dimensions.get('window').width}
decelerationRate={0}
overScrollMode={'never'}
snapToAlignment={'center'}
data={[
{
id: 'something',
other: 'data',
},
{
id: 'else',
other: 'data',
},
]}
onScrollEndDrag={() => console.log('Scroll end')}
horizontal={true}
viewabilityConfig={viewConfigRef.current}
onViewableItemsChanged={onViewChanged.current}
onScrollToIndexFailed={info => {
console.log('Scroll failed', info);
const wait = new Promise(resolve => setTimeout(resolve, 500));
wait.then(() => {
flatlistRef.current?.scrollToIndex({
index: info.index,
animated: true,
});
});
}}
renderItem={({item, index}) => {
return (
<View
style={{
marginRight: 20,
height: 20,
width: Dimensions.get('window').width - 20,
backgroundColor: 'red',
}}
/>
);
}}
/>
);
}
If you e.g. need to change component state inside onViewChanged method, you need to wrap these 2 props into Ref() when using Functional components / Hooks API.
Otherwise, it results in "Changing onViewableItemsChanged on the fly is not supported." error .
Also, you need to pass these props to Flalist by using .current syntax.
Here is GIF example: