I have a scrolling view of posts. Each post has a corresponding user and I have a header that shows the user info of the current visible post. With Flutter this was simple, I just wrapped the post widget with a visibility detector. With React Native this is not very easy. I’ve tried onViewableItemsChanged
but, since I am using a fuction
not a class
, that causes an error. I also tried some solutions that used onScroll
and onMomentumScrollEnd
but those all just stayed at index 0. How can I get the current index that is fully visible? If needed, I am fine with splitting up the pagination functions so I can just have a class with the UI and use onViewableItemsChanged
but I don’t know how to do that because the handleLoadMore
function is used in the UI.
export default function PostsListView() {
const [users, setUsers] = useState<User[]>([]);
const [posts, setPosts] = useState<Post[]>([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(true);
const [hasMore, setHasMore] = useState(true);
const onScroll = useCallback((event: any) => {
const slideSize = event.nativeEvent.layoutMeasurement.width;
const index = event.nativeEvent.contentOffset.x / slideSize;
const roundIndex = Math.round(index);
console.log("roundIndex:", roundIndex);
currentItem = roundIndex;
}, []);
useEffect(() => {
async function fetchPosts() {
setLoading(true);
const { data, error } = await supabase
.from("posts")
.select("*")
.order("date", { ascending: false })
.range((page - 1) * PAGE_SIZE, page * PAGE_SIZE - 1);
if (error) {
console.error(error);
showAlert();
setLoading(false);
return;
}
const newPosts = data.map((post: any) => new Post(post));
setPosts((prevPosts) => [...prevPosts, ...newPosts]);
setLoading(false);
setHasMore(data.length === PAGE_SIZE);
}
async function fetchUsers() {
const { data, error } = await supabase.from("posts").select("*");
if (error) {
showAlert();
console.error(error);
return;
}
const newUsers = data.map((user: any) => new User(user));
newUsers.forEach((user) => {
const userPosts = posts.filter((post) => post.uid === user.uid);
user.posts = [...user.posts, ...userPosts];
});
setUsers((prevUsers) => [...prevUsers, ...newUsers]);
}
fetchPosts();
fetchUsers();
}, [page]);
const handleLoadMore = () => {
if (!loading && hasMore) {
setPage((prevPage) => prevPage + 1);
}
};
const handleScroll = (event: any) => {
const index = Math.floor(
Math.floor(event.nativeEvent.contentOffset.x) /
Math.floor(event.nativeEvent.layoutMeasurement.width)
);
currentItem = index;
};
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
{loading ? (
<Text>Loading...</Text>
) : (
<FlatList
data={posts}
horizontal={false}
directionalLockEnabled={true}
renderItem={({ item }) => (
<View>
<HomePost
post={item}
user={
users.filter(function (u) {
return u.uid == item.uid;
})[0]
}
index={posts.indexOf(item)}
loading={loading}
/>
<SizedBox vertical={5} />
</View>
)}
keyExtractor={(item) => item.postId}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.1}
onScroll={onScroll}
onMomentumScrollEnd={onScroll}
/>
)}
</View>
);
}
4
Answers
You could get the currently viewable items with
onViewableItemsChanged
where you should get your information.If you provide a viewabilityConfig to the FlatList, you can use the onViewableItemsChanged event to learn which items are on screen. You just have to make sure that both the viewabilityConfig and onViewableItemsChanged values never change:
Demo
Take a look at the Intersection Observer API documentation which is an implementation you can use to detect when an element is visible on the screen or not.
Here’s a very simple example where the green div is "observed". Whether it is visible or not is marked in state.
Here’s a working sandbox
package.json
index.js
app.js
I’ve gone through and created a supabase app that resembles your use case and the onViewableItemsChanged approach should work:
Demo