Home > Mobile >  wait for onSnapshot fetching data
wait for onSnapshot fetching data

Time:10-07

I'm currently learning React Native (Expo). I want to use redux and react-native-firebase.

When I subscribe to firebase (onSnapshot) at startup of my app, it returns the data from firebase. But since onSnapchot doesn't return a promise, I can't use it for my app-loading component. Therefore, I also need to fetch the data from firebase to prevent the app from flicker.

The result is that at startup of my app I fetch the data twice.

So my question is: How can I wait for onSnapshot loading my data from firebase?

Thanks

const Manager = (props) => {
    //STATE
    const [init, setInit] = useState(false);

    //HOOKS
    const fetchData = useFetchData();
    useInitFirebaseSubscriptions();

    //FUNCTIONS
    async function onInit() {
        console.log('[MANAGER]: loading app...');
        await Promise.all([fetchData()]);
    }
    function onFinishedInit() {
        console.log('[MANAGER]: ...app loading successfull!');
        setInit(true);
    }

    //RETURN
    if (!init) {
        return <AppLoading startAsync={onInit} onFinish={onFinishedInit} onError={console.warn} />;
    } else {
        return props.children;
    }
};
export default Manager;

//INITIAL FETCH BEFORE RENDERING
export function useFetchData() {
    const dispatch = useDispatch();

    return async function () {
        try {
            await firestore()
                .collection('users')
                .get()
                .then((querySnapshot) => dispatch(actions.fetch(querySnapshot)));
        } catch (err) {
            console.log(err.message);
        }
    };
}
//INIT SUBSCRIPTIONS TO FIREBASE
export function useInitFirebaseSubscriptions() {
    const dispatch = useDispatch();

    useEffect(() => {
        console.log('[CONTROLLER]: subscribed to Firebase');
        const unsubscribe = firestore()
            .collection('users')
            .onSnapshot(
                (querySnapshot) => dispatch(action.fetch(querySnapshot)),
                (error) => console.log(error)
            );
        
        return () => {
            unsubscribe();
            console.log('[CONTROLLER]: unsubscribed from Firebase');
        };
    }, []);
}

[MANAGER]: loading app...
[MANAGER]: subscribed to Firebase
[USER_REDUCER]: fetched data
[USER_REDUCER]: fetched data
[MANAGER]: ...app loading successfull!

CodePudding user response:

I think you can accomplish your goal by adding some "loading" state in redux for when you are actively fetching data from firebase. Add the state and reducer cases specific to this data fetching/loading.

Example code:

export function useInitFirebaseSubscriptions() {
  const dispatch = useDispatch();

  useEffect(() => {
    console.log('[CONTROLLER]: subscribed to Firebase');

    dispatch(action.startFetch()); // <-- dispatch starting data fetch

    const unsubscribe = firestore()
      .collection('users')
      .onSnapshot(
        (querySnapshot) => {
          dispatch(action.fetch(querySnapshot));
          dispatch(action.completedFetch()); // <-- done fetching
        },
        (error) => {
          console.log(error);
          dispatch(action.completedFetch()); // <-- done fetching
        },
      );
        
    return () => {
      unsubscribe();
      console.log('[CONTROLLER]: unsubscribed from Firebase');
    };
  }, []);
};

Select the loading state from the redux store and conditionally render the loading UI, otherwise render the passed children.

const Manager = (props) => {
  const isFetchingData = useSelector(state => state.isFetchingData);

  if (isFetchingData) {
    return <AppLoadingIndicator />;
  }
  return props.children; // *
};

* Generally you may use some additional conditional rendering here depending on if data was actually fetched/returned and is just empty, or if there was an error, etc... basically provide a bit of a result status.

  • Related