react-native-web
react-native-web copied to clipboard
FlatList: onViewableItemsChanged is broken if pagingEnabled is true
The problem
Hi, we are using onViewableItemsChanged to do the impression tracking of items with FlatList, but we currently facing a problem with FlatList when pagingEnabled is true.
If pagingEnabled is true, we get all the items(include items not in the screen) from viewableItems in onViewableItemsChanged when scroll to the first item, get nothing when scroll to the second or after.
How to reproduce
Simplified test case: Codesandbox
Steps to reproduce:
- Go to this Codesandbox
- Open the console log
- Scroll the
FlatListand see the log ofviewableItems,viewableItemswill log all the items when scroll to the first item, which is unexpected. - Remove
pagingEnabledinFlatList - Scroll the
FlatListagain and see theviewableItemsworks as expected.
Expected behavior
Correctly get visible items from viewableItems in onViewableItemsChanged.
Environment (include versions). Did this work in previous versions? Here is our currently used version
- React Native for Web (version): ^0.12.0
- React (version): 16.8.6
- Browser: Chrome
But this issue also occurs with the Codesandbox version
- React Native for Web (version): 0.13.16
- React (version): 16.13.1
- Browser: Chrome
Inspection
Took a look into the code using chrome debugger, found something wrong below
https://github.com/necolas/react-native-web/blob/6624a70ccaf83988a0b1084cba9c85b6d85b0352/packages/react-native-web/src/exports/UIManager/index.js#L27-L31
In our case, the relativeRect.left has the same value of left returns by getRect(node), so all the items will have the same x which is 0, this will only happen if pagingEnabled is true
Workaround
Currently we override CellRendererComponent's onLayout to fix this problem
/**
* This is used to fix the incorrect offset if pagingEnabled is true on web
*/
const FixCellOffsetOnWeb = (props) => {
if (Platform.OS === "web") {
const { onLayout, ...other } = props;
const fixOffsetOnLayout = (e) => {
if (onLayout) {
onLayout({
...e,
nativeEvent: {
...e.nativeEvent,
layout: {
...e.nativeEvent.layout,
// workaround: override the x offset
x: other.index * ITEM_WIDTH
}
}
});
}
};
return <View {...other} onLayout={fixOffsetOnLayout} />;
}
return <View {...props} />;
};
<FlatList
CellRendererComponent={FixCellOffsetOnWeb}
horizontal
pagingEnabled
// ... other props needed ...
/>
This is happening because:
- An extra
divis added around the items to supportpagingEnabled. - React Native's
onLayoutis relative to the parent view. - But the parent view is different between web and native implementations.
This might also be an issue when using sticky headers: https://github.com/necolas/react-native-web/blob/0.14.6/packages/react-native-web/src/exports/ScrollView/index.js#L157-L177
I don't think there's a good way to deal with this other than: either remove the wrapping div and lose pagingEnabled etc support, or rewrite the VirtualizedList logic so it doesn't make this assumption.
React Native has not been good at patching JS code for other platforms, and VirtualizedList itself isn't great in terms of performance. But I'm not interested in forking and maintaining another slightly different VirtualizedList implementation when the JS one in React Native should be patched (or extracted to another package) to improve support for out-of-tree platforms.
@necolas Thanks for the explanation, currently we'll just stick with the workaround or using IntersectionObserver
Closing this old issue because VirtualizedList has been updated a few times since, and it will be developed exclusively out of the RN monorepo in the future. There may still be an issue in RNW related to how pagingEnabled is implemented, so feel free to create a new issue with latest info if needed.