Junaid Razaq
Junaid Razaq

Reputation: 251

How to wait for Firebase data to be fetched before progressing?

I am fetching data from Fire store in real-time with .onSnapshot and it works great, I am receiving the data as expected. The problem is that I am receiving multiple sets of data, and the component does not wait until all the data is received before rendering.

So my question is, with my current code, is their a way in which I can wait for all sets of my data to be fetched before displaying them?

My current code is:

import React, {useEffect, useState} from 'react';
import {ActivityIndicator, Dimensions, Text, View} from 'react-native';
import firestore from '@react-native-firebase/firestore';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import FolloweringScreens from './FolloweringScreens';
import {TouchableOpacity} from 'react-native-gesture-handler';

const {width, height} = Dimensions.get('screen');

function Following({urlname, navigation}) {
  const [followingData, setfollowingData] = useState([]);

  // Follower counts, displayname, image
  const fetchData = () => {
    const dataRef = firestore().collection('usernames');

    dataRef
      .doc(urlname)
      .collection('Following')
      .onSnapshot((snapshot) => {
        snapshot.forEach((doc) => {
          dataRef.doc(doc.id.toLowerCase()).onSnapshot((followerDoc) => {
            const data = followerDoc.data();
            setfollowingData((prev) => [
              ...prev,
              {
                profileName: doc.id,
                displayName: data.userName,
                followerCount:
                  data.followers !== undefined ? data.followers : 0,
                followingCount:
                  data.following !== undefined ? data.following : 0,
                image: data.imageUrl ? data.imageUrl : null,
              },
            ]);
          });
        });
      });
  };

  useEffect(() => {
    fetchData();
  }, []);

  return (
    <>
      <View
        style={{
          left: width * 0.04,
          top: 50,
          flexDirection: 'row',
          alignItems: 'center',
          width: '80%',
          height: '4%',
          marginBottom: 5,
        }}>
        {/* {console.log('followin', followingData)} */}
        <TouchableOpacity onPress={() => navigation.openDrawer()}>
          <Icon name="menu" color="#222" size={30} />
        </TouchableOpacity>
        <Text style={{left: width * 0.05}}>Following</Text>
      </View>

      {followingData === [] ? (
        <ActivityIndicator size="large" color="black" />
      ) : (
        <>
          <FolloweringScreens data={followingData} />
        </>
      )}
    </>
  );
}

export default Following;

Upvotes: 0

Views: 3097

Answers (2)

Junaid Razaq
Junaid Razaq

Reputation: 251

So I managed to fix it somehow. Thanks to Julian for the help

What I did was create an array of promises which will be executed whenever the data changes. The code is:

import React, {useCallback, useEffect, useState} from 'react';
import {ActivityIndicator, Dimensions, Text, View} from 'react-native';
import firestore from '@react-native-firebase/firestore';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import FolloweringScreens from './FolloweringScreens';
import {TouchableOpacity} from 'react-native-gesture-handler';

const {width, height} = Dimensions.get('screen');

function Following({urlname, navigation}) {
  const [followingData, setfollowingData] = useState();
  const [loading, setLoading] = useState(true);

  // Following counts, displayname, image
  const fetchData = useCallback(() => {
    const dataRef = firestore().collection('usernames');

    dataRef
      .doc(urlname)
      .collection('Following')
      .limit(25)
      .onSnapshot((snapshot) => {
        let promises = [];
        snapshot.forEach((doc) => {
          const promise = dataRef
            .doc(doc.id.toLowerCase())
            .get()
            .then((followerDoc) => {
              const data = followerDoc.data();

              return {
                profileName: doc.id,
                displayName: data.displayName
                  ? data.displayName
                  : data.userName,
                followerCount:
                  data.followers !== undefined ? data.followers : 0,
                followingCount:
                  data.following !== undefined ? data.following : 0,
                image: data.imageUrl ? data.imageUrl : null,
              };
            });
          promises.push(promise);
        });
        Promise.all(promises)
          .then((res) => setfollowingData(res))
          .then(setLoading(false));
      });
  }, []);

  useEffect(() => {
  const dataRef = firestore().collection('usernames');

    const cleanup = dataRef
      .doc(urlname)
      .collection('Following')
      .limit(25)
      .onSnapshot(fetchData);

    return cleanup;

    // fetchData();
  }, [urlname, fetchData]);

  return (
    <>
      <View
        style={styles}>
        <TouchableOpacity onPress={() => navigation.openDrawer()}>
          <Icon name="menu" color="#222" size={30} />
        </TouchableOpacity>
        <Text style={{left: width * 0.05}}>Following</Text>
          </View>

      {loading ? (
        <ActivityIndicator size="large" color="black" />
      ) : (
        <>
          <FolloweringScreens data={followingData} />
        </>
      )}
    </>
  );
}

export default Following;

Upvotes: 1

Julian Kleine
Julian Kleine

Reputation: 1547

Use a state isLoading default true, then set isLoading to false once a snapshot resolves, and show a loading indicator on isLoading true and show your ui when isLoading false. Then you get updates pushed to your state and the user will see the data once it's fully loaded.

Would also use something close to this. One thing that is quite weird is that you push every snapshot change to an array, so in other words over time this array holds a history of changes of the same object. Intentional?

function Following({ urlname }) {
  const [followingData, setfollowingData] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  // Follower counts, displayname, image
  const onSnapshot = useCallback((snapshot) => {
    snapshot.forEach((doc) => {
      dataRef.doc(doc.id.toLowerCase()).onSnapshot((followerDoc) => {
        const data = followerDoc.data();
        // push new document data into an array
        setfollowingData((prev) => [
          ...prev,
          {
            profileName: doc.id,
            displayName: data.userName,
            followerCount: data.followers !== undefined ? data.followers : 0,
            followingCount: data.following !== undefined ? data.following : 0,
            image: data.imageUrl ? data.imageUrl : null
          }
        ]);
        // or set the new data to state, by just setting the document data
        setfollowingData(data);
        setIsLoading(false);
      });
    });
  }, []);

  useEffect(() => {
    const dataRef = firestore().collection("usernames");

    const cleanup = dataRef
      .doc(urlname)
      .collection("Following")
      .onSnapshot(onSnapshot);

    return cleanup;
  }, [onSnapshot, urlname]);

  return (
    <>
      {isLoading && <p>Loading</p>}
      {!isLoading && <p>Show data {followingData.length}</p>}
    </>
  );
}

Upvotes: 4

Related Questions