3 votes

React Native - Firebase onSnaphot + pagination FlatList

INTRODUCTION

J'ai une liste d'éléments avec la propriété "date" stockée dans mon FireStore. Dans le code client, j'ai une FlatList avec tous ces éléments classés par "date" (le premier élément est l'élément le plus récent, le second, l'élément que j'ai téléchargé avant l'élément qui apparaît en premier, ...).

Le problème est que je n'obtiens que 5 éléments (mais c'est parce que je ne veux pas obtenir 100 éléments en une seule fois), et je ne sais pas comment combiner cela avec onEndReached de FlatList (car c'est un agent listener qui doit être détaché lorsque le composant se démonte) pour obtenir plus d'éléments en suivant le même ordre.

Une idée sur la manière de faire fonctionner ce système ? J'ai commenté "<------------" sur les lignes du code que je pourrais être amené à modifier.

BASE DE DONNÉES FIRESTORE

Items -> user.uid -> userItems :

   {
     ...
     date: 1/1/1970
   },
   {
     ...
     date: 2/1/1970
   },
   ...
   {
     ...
     date: 31/1/1970 
   }

COMMENT MA FLATLIST DOIT ÊTRE RENDUE :

Les éléments de la FlatList dans l'ordre :

{ // 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'? */} /> // <----------

Ce dont j'ai besoin, c'est de récupérer les 5 autres éléments les plus récents lorsque la fin de la liste est atteinte, puis de les ajouter à l'élément fond de la liste


MISE À JOUR (ma meilleure approche pour l'instant)

  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();
    };
  }, []);

Avec ce code, je peux récupérer une quantité initiale d'articles la première fois, puis la quantité suivante lorsque j'atteins la fin de ma FlatList. Mais pour une raison quelconque, l'état n'est pas mis à jour dans le listener... donc quand un nouvel élément est téléchargé, tous les éléments que j'avais récupérés auparavant disparaissent de la FlatList et ils sont récupérés à nouveau quand la fin de la FlatList est atteinte.

3voto

Raul Points 991

Bon, après quelques heures de codage, j'ai trouvé une solution. Je pense que ce n'est pas la meilleure car il serait préférable d'utiliser onSnapshot également lorsque la fin de la FlatList est atteinte, mais je ne sais pas si c'est possible avec l'implémentation onSnapshot de Firestore.

La solution est basée sur le code "ma meilleure approche" qui figure dans la question.

Algorithme :

  1. Dans un premier temps, je crée l'écouteur de base de données en temps réel, qui effectue simplement un onSnapshot, puis j'appelle ma fonction onItemsCollectionUpdate (en passant le snapshot en argument), qui peut parfaitement accéder à l'état mis à jour de l'application (puisqu'il n'est pas dans l'agent listener)

  2. Lorsque nous sommes dans la fonction onItemsCollectionUpdate, nous récupérons simplement les éléments de l'instantané et les ajoutons à l'état des éléments.

  3. Lorsque la fin de la FlatList est atteinte, nous appelons simplement la fonction "getItems", qui effectue une récupération statique des données du Firestore (je veux dire, en utilisant la fonction obtenir de Firebase) et l'ajouter à l'état des éléments.

  4. Lorsque le composant est démonté, détachez l'agent d'écoute.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X