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>
);
}
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:
import { useState, useEffect, useRef, useCallback } from 'react';
import { Text, View, StyleSheet, FlatList, Image } from 'react-native';
import Constants from 'expo-constants';
// You can import from local files
import AssetExample from './components/AssetExample';
// or any pure javascript modules available in npm
import { Card } from 'react-native-paper';
const API_URL = 'https://random-data-api.com/api/v2/users?size=25';
export default function App() {
const [posts, setPosts] = useState([]);
const [visibleItems, setVisibleItems] = useState([]);
// wrapped in ref so that re-renders doesnt recreate it
const viewabilityConfig = useRef({
minimumViewTime: 100,
itemVisiblePercentThreshold: '90%',
}).current;
// wrapped in useCallback so that re-renders doesnt recreate it
const onViewableItemsChanged = useCallback(({ viewableItems }) => {
setVisibleItems(viewableItems.map(({ item }) => item));
}, []);
useEffect(() => {
fetch(API_URL)
.then((data) => data.json())
.then(setPosts);
}, []);
return (
<View style={styles.container}>
{visibleItems.length > 0 && (
<Text>
Currently visible:{' '}
{visibleItems
.map((item) => item.first_name + ' ' + item.last_name)
.join(', ')}
</Text>
)}
<View style={styles.flatlistContainer}>
<FlatList
data={posts}
renderItem={(props) => <Item {...props} />}
viewabilityConfig={viewabilityConfig}
onViewableItemsChanged={onViewableItemsChanged}
/>
</View>
</View>
);
}
const Item = ({ item }) => {
return (
<View style={styles.itemContainer}>
<Text>
{item.first_name} {item.last_name}
</Text>
<Image
source={{ uri: item.avatar }}
style={{ width: 100, height: 100 }}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
flatlistContainer: {
width: '100%',
height: 500,
backgroundColor: 'lightblue',
},
itemContainer: {
justifyContent: 'center',
alignItems: 'center',
margin: 10,
},
});
Demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With