Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get current index/visible item of FlatList

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>
  );
}
like image 903
Globe Avatar asked Jan 26 '26 04:01

Globe


1 Answers

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

like image 187
PhantomSpooks Avatar answered Jan 28 '26 20:01

PhantomSpooks