React + Firestore - Bidirectional pagination (query new docs in a particular order)

Viewed 77

INTRODUCTION

I am implementing a pull-to-refresh system where, when refreshing, I fetch new user posts.

So, I am rendering the docs data like this in the UI (sorted by timestamp, in descending order):

  • 1st render (not refreshed)

    Post 4 - Timestamp: 9999 <--- Most recent post
    Post 3 - Timestamp: 9998
    Post 2 - Timestamp: 9997
    Post 1 - Timestamp: 9996 <--- Oldest post
    
  • 2nd render (after refreshing)

    Post 6 - Timestamp 10001 <--- Most recent post
    Post 5 - Timestamp 10000 
    
    /* ====== OLD DATA ====== */
    
    Post 4 - Timestamp: 9999 
    Post 3 - Timestamp: 9998
    Post 2 - Timestamp: 9997
    Post 1 - Timestamp: 9996 <--- Oldest post
    

As I am rendering the data in a FlatList, I am providing the user a way to "fetch more old user posts" when he/she scrolls to the bottom of the list (on end reached).

QUERY CODE

This is my current implementation for the api method (where I perform the query):

export async function getUserPosts(
  userId,
  getOldPosts = true,
  startAfter,
  limit = MAX_USER_POSTS_TO_RETRIEVE
) {
  const userPostsRef = firestore
    .collection("posts")
    .doc(userId)
    .collection("userPosts");

  const query = userPostsRef
    .orderBy("date", getOldPosts ? "desc" : "asc")
    .startAfter(startAfter);

  const querySnapshot = await query.limit(limit).get();

  const posts = querySnapshot.docs.map((doc) => parsePost(doc));

  // Does the user have more posts to load?
  const hasMoreToLoad = querySnapshot.size === limit;

  // Calculate the new startAfter
  if (querySnapshot.size) {
    startAfter =
      querySnapshot.docs[getOldPosts ? querySnapshot.docs.length - 1 : 0];
  }

  return { posts, hasMoreToLoad, startAfter };
}

HOW I USE IT

Basically, to implement the bidirectional pagination, I am using two cursors:

export default function useFetchUserPosts(userId) {
   const [posts, setPosts] = useState([]);
   ...

   const newestPostCursor = useRef(new Date());
   const oldestPostCursor = useRef(new Date());

   ...
}

newestPostCursor is used as startAfter for querying more new posts, and oldestPostCursor is used the same way but in the opposite direction.

This is how I call my api method in order to fetch the user's recent posts:

export default function useFetchUserPosts(userId) {
   const [posts, setPosts] = useState([]);
   ...

   const newestPostCursor = useRef(new Date());
   const oldestPostCursor = useRef(new Date());

   ...

   const getMoreNewPosts = async (limit) => {
      if (isFetching.current) return;

      isFetching.current = true;

      try {
         const { posts: newPosts, startAfter: newStartAfter } = await getUserPosts(
           userId,
           false,
           newestPostCursor.current,
           limit
         );

         newestPostCursor.current = newStartAfter;

         if (newPosts.length) {
            setPosts((prevPosts) => [...newPosts, ...prevPosts]);
         }

         isFetching.current = false;
      } catch (err) {
          // TODO - Show error in a toast
          console.log(err);
      }
   };

   ...
}

Same thing for old posts, but using the other cursor.

Problem

The old posts are retrieved correctly, in the order I want...

But, the new posts, (for refreshings) are retrieved as:

[Post 5, Post 6]

and not as

[Post 6, Post 5]  

The query works fine, it returns me the docs I want, but not in the order I need... I know that I can just reverse the list in the client side, but is it possible to get the docs in that order without changing the order in the frontend?

I think that the problem is with the startAfter... maybe I should try to use the endAt(newestPostCursor) but... I don't find a way to do it correctly, as the initial value of newestPostCursor is new Date()... so, in the first refresh, due to the .limit(N) some docs might be skipped.

Any help?

0 Answers
Related