Carl.B
Carl.B

Reputation: 31

Redux Saga yield call(someAPI) is not waiting for API call to complete

I'm making an app that fetch events object from firestore to display it on a map.

I implemented redux-saga to do the async call to firestore API on componentDidMount in order to display the results on the map. I have 3 actions (LOAD_EVENTS_LOADING / LOAD_EVENTS_SUCCESS / LOAD_EVENTS_ERROR) so that I can display a loading component before rendering the results. saga.js :

> import { put, call, takeLatest } from 'redux-saga/effects' import {
> getEventsFromGeoloc }  from '../../firebaseAPI/APImethods'
> 
> function* fetchEvents(action) {
>     try {
> 
>         const events = yield call(getEventsFromGeoloc, {latMarker : action.payload.latMarker, longMarker: action.payload.longMarker,
> circleRadius: action.payload.circleRadius});
>         yield put({type: 'LOAD_EVENTS_SUCCESS', fetchedEvents: events});
>     } catch (e) {
>         yield put({type: 'LOAD_EVENTS_ERROR', error: e.message});
>     } }
> 
> export function* eventsSaga() {
>     yield takeLatest('LOAD_EVENTS_LOADING', fetchEvents); }
> 
>  export default eventsSaga;

My problem is that in my saga the action "LOAD_EVENTS_SUCCESS" is dispatched before the API call ends.

How do I make sure the API call is completed before dispatching the "LOAD_EVENTS_SUCCESS" action ? Thanks for your help !

Here is my API method :

import firebase from 'react-native-firebase';
import { GeoFirestore } from 'geofirestore';

export const getEventsFromGeoloc = (payload) => {

    let events =[]
    const geoFirestore = new GeoFirestore(firebase.firestore());
    const geoCollection = geoFirestore.collection('events');
    const query = geoCollection.near({
        center: new firebase.firestore.GeoPoint(payload.latMarker, payload.longMarker),
        radius: payload.circleRadius
    });

    query.get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            // doc.data() is never undefined for query doc snapshots
            console.log(doc.id, " => ", doc.data());
            const idEvent = doc.id
            const eventData = doc.data()
            events.push({idEvent, eventData})
        });
    })
    .catch(function(error) {
        console.log("Error getting documents: ", error);
    });

    return events;
}

Upvotes: 3

Views: 5216

Answers (4)

Carl.B
Carl.B

Reputation: 31

thanks all for your answers. I did a saga with my getEventsFromGeoloc and with a yield for the query its working like I expected : It waits for the complete query to run and then return the array of events and dispatch the LOAD_EVENTS_SUCCESS action.

Here is my saga :

import { put, takeLatest } from 'redux-saga/effects'
import firebase from 'react-native-firebase';
import { GeoFirestore } from 'geofirestore';

function* getEventsFromGeoloc(action)  {
    try{
    let events =[]
    const geoFirestore = new GeoFirestore(firebase.firestore());
    const geoCollection = geoFirestore.collection('events');
    const query = geoCollection.near({
        center: new firebase.firestore.GeoPoint(action.payload.latMarker, action.payload.longMarker),
        radius: action.payload.circleRadius
    });

    yield query.get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            // doc.data() is never undefined for query doc snapshots
            console.log(doc.id, " => ", doc.data());
            const idEvent = doc.id
            const eventData = doc.data()
            events.push({idEvent, eventData})
        })})
        yield put({type: 'LOAD_EVENTS_SUCCESS', fetchedEvents: events});
    }
    catch(error) {
        console.log("Error getting documents: ", error);
        yield put({type: 'LOAD_EVENTS_ERROR', error: error.message});
    }
}


export function* eventsSaga() {
    yield takeLatest('LOAD_EVENTS_LOADING', getEventsFromGeoloc);
}

 export default eventsSaga;

Upvotes: 0

Doug Watkins
Doug Watkins

Reputation: 1448

Firebase works differently than you would expect. The firebase queries return immediately, they don't wait for the query to finish and be processed by your code before it returns.

What I ended up doing in my own apps, was instead of calling the success/fail action from the original saga function, I called them from within my query callback, something like this:

query.get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            // doc.data() is never undefined for query doc snapshots
            console.log(doc.id, " => ", doc.data());
            const idEvent = doc.id
            const eventData = doc.data()
            events.push({idEvent, eventData})
        });
        yield put({type: 'LOAD_EVENTS_SUCCESS', fetchedEvents: events});
    })
    .catch(function(error) {
        console.log("Error getting documents: ", error);
        yield put({type: 'LOAD_EVENTS_ERROR', error: error.message});
    });

NOTE: This will also require you make your getEventsFromGeoloc function a generator function, or a saga, to use the yield statements as well as import the put saga effect.

OTHER NOTE: You could make the function more generic by passing in a callback function for success and failure and just call the callback instead of hard coding the action you are dispatching like this. Or you could pass in the action type and payload keys to build the action inside the completion callback for firebase.

Let me know if that wasn't helpful enough, or if you have questions about what I've put up here.

Upvotes: 0

Prabhat Mishra
Prabhat Mishra

Reputation: 1021

call(fn, ...args)
fn: Function - A Generator function, or normal function which either returns a Promise as result, or any other value.
Refer this docs

In your case you are returning events from your getEventsFromGeoloc func even when your response has not yet arrived.

Upvotes: 0

Federkun
Federkun

Reputation: 36924

getEventsFromGeoloc is not returning a promise for redux-saga to await, that's the problem

Just return a promise that resolve with the events:

export const getEventsFromGeoloc = (payload) => {
    // ...

    return query.get()
    .then(function(querySnapshot) {
        const events = []
        querySnapshot.forEach(function(doc) {
            // doc.data() is never undefined for query doc snapshots
            console.log(doc.id, " => ", doc.data());
            const idEvent = doc.id
            const eventData = doc.data()
            events.push({idEvent, eventData})
        });

        return events
    })
    .catch(function(error) {
        console.log("Error getting documents: ", error);
    });
}

Upvotes: 4

Related Questions