INTRODUCTION
I have a list of items with the prop "date" stored on my FireStore. In the client code, I have a FlatList with all of those items ordered by "date" (the first element is the most recent item, the second one, the item I uploaded before the element which appears first, ...)
The problem is that I only get 5 items (but it is because I don't want to get 100 items at once), and I don't know how to combine this with the FlatList's onEndReached (as it is a listener agent that has to be detached when component unmounts) to get more items following the same order.
Any ideas how to make this work? I have commented "<------------" on the lines of the code that I might have to change.
FIRESTORE DATABASE
Items -> user.uid -> userItems:
{
...
date: 1/1/1970
},
{
...
date: 2/1/1970
},
...
{
...
date: 31/1/1970
}
HOW MY FLATLIST HAS TO BE RENDERED:
FlatList items in order:
{ // The most recent one appears at the top of the list
...
date: 31/1/1970
},
...
{
...
date: 2/1/1970
},
{
...
date: 1/1/1970
},
CODE
const [startItem, setStartItem] = useState(null);
useEffect(() => {
const { firebase } = props;
let itemsArray = [];
// Realtime database listener
const unsuscribe = firebase // <------- With this I get the 5 most recent items when component mounts, or only one if the user has uploaded it after the component mounts
.getDatabase()
.collection("items")
.doc(firebase.getCurrentUser().uid)
.collection("userItems")
.orderBy("date") // Sorted by upload date <------------------
.startAfter(startItem && startItem.date) // <-----------------------
.limitToLast(5) // To avoid getting all items at once, we limit the fetch to 5 items <----------
.onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach((change) => {
if (change.type === "added") {
// Get the new item
const newItem = change.doc.data();
// Add the new item to the items list
itemsArray.unshift(newItem);
}
});
// Reversed order so that the last item is at the top of the list
setItems([...itemsArray]); // Shallow copy of the existing array -> Re-render when new items added
setIsLoading(false);
// Change the start item
setStartItem(itemsArray[itemsArray.length - 1]);
});
return () => {
// Detach the listening agent
unsuscribe();
};
}, []);
...
<CardList data={items} isLoading={isLoading} onEndReached={/*how to call the function 'unsuscribe'? */} /> // <----------
What I need is to get the other next 5 more recent items when the end of the list is reached, and then, add them to the bottom of the list
UPDATE (My best approach for now)
const [items, setItems] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [start, setStart] = useState(null);
const limitItems = 5;
const getItems = () => {
/*
This function gets the initial amount of items and returns a
real time database listener (useful when a new item is uploaded)
*/
const { firebase } = props;
// Return the realtime database listener
return firebase
.getDatabase()
.collection("items")
.doc(firebase.getCurrentUser().uid)
.collection("userItems")
.orderBy("date") // Sorted by upload date
.startAt(start)
.limitToLast(limitItems)
.onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
let itemsArray = [...items]; // <------- Think the error is here
console.log(`Actual items length: ${itemsArray.length}`); // <-- Always 0 WHY?
console.log(`Fetched items: ${changes.length}`); // 5 the first time, 1 when a new item is uploaded
changes.forEach((change) => {
if (change.type === "added") {
// Get the new fetched item
const newItem = change.doc.data();
// Add the new fetched item to the head of the items list
itemsArray.unshift(newItem);
}
});
// The last item is at the top of the list
setItems([...itemsArray]); // Shallow copy of the existing array -> Re-render when new items added
// Stop loading
setIsLoading(false);
// If this is the first fetch...
if (!start && itemsArray.length) {
// Save the startAt snapshot
setStart(itemsArray[itemsArray.length - 1].date);
}
});
};
const getMoreItems = () => {
/*
This funciton gets the next amount of items
and is executed when the end of the FlatList is reached
*/
const { firebase } = props;
// Start loading
setIsLoading(true);
firebase
.getDatabase()
.collection("items")
.doc(firebase.getCurrentUser().uid)
.collection("userItems")
.orderBy("date", "desc")
.startAfter(start)
.limit(limitItems)
.get()
.then((snapshot) => {
let itemsArray = [...items];
snapshot.forEach((doc) => {
// Get the new fethed item
const newItem = doc.data();
// Push the new fetched item to tail of the items array
itemsArray.push(newItem);
});
// The new fetched items will be at the bottom of the list
setItems([...itemsArray]); // Shallow copy of the existing array -> Re-render when new items added
// Stop loading
setIsLoading(false);
// Save the startAt snapshot everytime this method is executed
setStart(itemsArray[itemsArray.length - 1].date);
});
};
useEffect(() => {
// Get a initial amount of items and create a real time database listener
const unsuscribe = getItems();
return () => {
// Detach the listening agent
unsuscribe();
};
}, []);
With this code I can fetch an initial amount of items the first time, and then the next amount when I reach the end of my FlatList. But for some reason the state is not updated inside the listener... so when a new item is uploaded, all the items I got before disapears from the FlatList and they are fethed again when the end of the FlatList is reached.