'FlatList ScrollView Error on any State Change - Invariant Violation: Changing onViewableItemsChanged on the fly is not supported
onViewableItemsChanged does not seem to work when there is a state change in the app. Is this correct?
Seems like it wouldn't be very useful if this were the case....
Otherwise, users will be forced to us onScroll in order to determine position or something similar...
Steps to Reproduce
- Please refer to snack
- Repo has also been uploaded at github
- Any state change produces an error when using
onViewableItemsChanged - What does this error even mean?
Note: Placing the onViewableItemsChanged function in a const outside the render method also does not assist...
<FlatList
data={this.state.cardData}
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
onViewableItemsChanged={(info) =>console.log(info)}
viewabilityConfig={{viewAreaCoveragePercentThreshold: 50}}
renderItem={({item}) =>
<View style={{width: width, borderColor: 'white', borderWidth: 20,}}>
<Text>Dogs and Cats</Text>
</View>
}
/>
Actual Behavior
Error

Solution 1:[1]
Based on @woodpav comment. Using functional components and Hooks.
Assign both viewabilityConfig and onViewableItemsChanged to refs and use those. Something like below:
const onViewRef = React.useRef((viewableItems)=> {
console.log(viewableItems)
// Use viewable items in state or as intended
})
const viewConfigRef = React.useRef({ viewAreaCoveragePercentThreshold: 50 })
<FlatList
horizontal={true}
onViewableItemsChanged={onViewRef.current}
data={Object.keys(cards)}
keyExtractor={(_, index) => index.toString()}
viewabilityConfig={viewConfigRef.current}
renderItem={({ item, index }) => { ... }}
/>
Solution 2:[2]
The error "Changing onViewableItemsChanged on the fly is not supported" occurs because when you update the state, you are creating a new onViewableItemsChanged function reference, so you are changing it on the fly.
While the other answer may solve the issue with useRef, it is not the correct hook in this case. You should be using useCallback to return a memoized callback and useState to get the current state without needing to create a new reference to the function.
Here is an example that save all viewed items index on state:
const MyComp = () => {
const [cardData] = useState(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']);
const [viewedItems, setViewedItems] = useState([]);
const handleVieweableItemsChanged = useCallback(({ changed }) => {
setViewedItems(oldViewedItems => {
// We can have access to the current state without adding it
// to the useCallback dependencies
let newViewedItems = null;
changed.forEach(({ index, isViewable }) => {
if (index != null && isViewable && !oldViewedItems.includes(index)) {
if (newViewedItems == null) {
newViewedItems = [...oldViewedItems];
}
newViewedItems.push(index);
}
});
// If the items didn't change, we return the old items so
// an unnecessary re-render is avoided.
return newViewedItems == null ? oldViewedItems : newViewedItems;
});
// Since it has no dependencies, this function is created only once
}, []);
function renderItem({ index, item }) {
const viewed = '' + viewedItems.includes(index);
return (
<View>
<Text>Data: {item}, Viewed: {viewed}</Text>
</View>
);
}
return (
<FlatList
data={cardData}
onViewableItemsChanged={handleVieweableItemsChanged}
viewabilityConfig={this.viewabilityConfig}
renderItem={renderItem}
/>
);
}
You can see it working on Snack.
Solution 3:[3]
You must pass in a function to onViewableItemsChanged that is bound in the constructor of the component and you must set viewabilityConfig as a constant outside of the Flatlist.
Example:
class YourComponent extends Component {
constructor() {
super()
this.onViewableItemsChanged.bind(this)
}
onViewableItemsChanged({viewableItems, changed}) {
console.log('viewableItems', viewableItems)
console.log('changed', changed)
}
viewabilityConfig = {viewAreaCoveragePercentThreshold: 50}
render() {
return(
<FlatList
data={this.state.cardData}
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
onViewableItemsChanged={this.onViewableItemsChanged}
viewabilityConfig={this.viewabilityConfig}
renderItem={({item}) =>
<View style={{width: width, borderColor: 'white', borderWidth: 20,}}>
<Text>Dogs and Cats</Text>
</View>}
/>
)
}
}
Solution 4:[4]
Move the viewabilityConfig object to the constructor.
constructor() {
this.viewabilityConfig = {
viewAreaCoveragePercentThreshold: 50
};
}
render() {
return(
<FlatList
data={this.state.cardData}
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
onViewableItemsChanged={(info) =>console.log(info)}
viewabilityConfig={this.viewabilityConfig}
renderItem={({item}) =>
<View style={{width: width, borderColor: 'white', borderWidth: 20,}}>
<Text>Dogs and Cats</Text>
</View>
}
/>
)
}
Solution 5:[5]
const handleItemChange = useCallback( ({viewableItems}) => {
console.log('here are the chaneges', viewableItems);
if(viewableItems.length>=1)
viewableItems[0].isViewable?
setChange(viewableItems[0].index):null;
},[])
try this one it work for me
Solution 6:[6]
Setting both onViewableItemsChanged and viewabilityConfig outside the flatlist solved my problem.
const onViewableItemsChanged = useCallback(({ viewableItems }) => {
if (viewableItems.length >= 1) {
if (viewableItems[0].isViewable) {
setItem(items[viewableItems[0].index]);
setActiveIndex(viewableItems[0].index);
}
}
}, []);
const viewabilityConfig = {
viewAreaCoveragePercentThreshold: 50,
};
I'm using functional component and my flatlist looks like this
<Animated.FlatList
data={items}
keyExtractor={item => item.key}
horizontal
initialScrollIndex={activeIndex}
pagingEnabled
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
ref={flatlistRef}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { x: scrollX } } }],
{ useNativeDriver: false },
)}
contentContainerStyle={{
paddingBottom: 10,
}}
showsHorizontalScrollIndicator={false}
renderItem={({ item }) => {
return (
<View style={{ width, alignItems: 'center' }}>
<SomeComponent item={item} />
</View>
);
}}
/>
Solution 7:[7]
Remove your viewabilityConfig prop to a const value outside the render functions as well as your onViewableItemsChanged function
Solution 8:[8]
Sombody suggest to use extraData property of Flatlist to let Flatlist notice, that something changed.
But this didn't work for me, here is what work for me:
Use key={this.state.orientation} while orientation e.g is "portrait" or "landscape"... it can be everything you want, but it had to change, if the orientation changed.
If Flatlist notice that the key-property is changed, it rerenders.
works for react-native 0.56
Solution 9:[9]
this works for me, is there any way to pass an additional argument to onViewRef? Like in the below code how can i pass type argument to onViewRef. Code:
function getScrollItems(items, isPendingList, type) {
return (
<FlatList
data={items}
style={{width: wp("100%"), paddingLeft: wp("4%"), paddingRight: wp("10%")}}
horizontal={true}
keyExtractor={(item, index) => index.toString()}
showsHorizontalScrollIndicator={false}
renderItem={({item, index}) => renderScrollItem(item, index, isPendingList, type)}
viewabilityConfig={viewConfigRef.current}
onViewableItemsChanged={onViewRef.current}
/>
)
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 | Rishav |
| Solution 4 | Dagobert Renouf |
| Solution 5 | Pravin Ghorle |
| Solution 6 | Fabi Mendes |
| Solution 7 | AndrewK |
| Solution 8 | suther |
| Solution 9 | Ashish Guleria |
