1
votes

I'm building something like a Messenger using React + Redux and Real-Time Database from Firebase.

I'm used to retrieve data from an API like this:

export const fetchContacts = () => {
    return async dispatch => {
        dispatch(fetchContactsStart());

        try {
            const response = await axios.get('....');

            dispatch(fetchContactsSuccess(...));
        } catch(e) {
            dispatch(fetchContactsFail(...));
        }
    }
}

Now in firebase I have the following data: enter image description here

What I need is to get all user contacts and then get user info associated with those contacts and build a structure like: [{email: ..., username: ...}, {email: ..., username: ...}, ...]. After getting all the data in that format I want to dispatch the action to my reducer.

Right now I have the following code:

export const fetchContacts = () => {
    return dispatch => {
        dispatch(fetchContactsStart());

        const userEmail = firebase.auth().currentUser.email;
        let data = [];

        firebase.database().ref(`users_contacts/${Base64.encode(userEmail)}`)
            .on('value', contacts => contacts.forEach(contact => {
                firebase.database().ref(`users/${Base64.encode(contact.val().contact)}`)
                    .on('value', user => {
                        data.push({ email: contact.val().contact, username: user.val().username });
                        console.log(data)
                    })
            }
        ))
    }
}

This works but I don't know how to dispatch the action only when the data is fully formed, is there any solution for this? Thanks for the help!

1

1 Answers

1
votes

When you're waiting for multiple asynchronous operations to complete, the solution is usually to use Promise.all. In this case, that'd look something like this:

export const fetchContacts = () => {
  return dispatch => {
    dispatch(fetchContactsStart());

    const userEmail = firebase.auth().currentUser.email;
    let data = [];

    firebase.database().ref(`users_contacts/${Base64.encode(userEmail)}`).once('value', contacts => {
      let promises = [];
      let map = {};
      contacts.forEach(contact => {
        const contactId = Base64.encode(contact.val().contact);
        map[contactId] = contact.val().contact;
        promises.push(firebase.database().ref(`users/${contactId}`).once('value'));
      });
      Promise.all(promises).then((users) => {
        users.forEach((user) =>
          data.push({ email: map[user.key], username: user.val().username });
        })
        // TODO: dispatch the result here
      }
    ))
  }
}

The main changes in here:

  • Now uses once() for loading the user data, so that is only loads once and returns a promise.
  • Uses Promise.all to wait until all profiles are loaded.
  • Added a map to look up the email address in the inner callback.