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 post2nd 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?