0

I am building an app with react/ redux for managing Collection of Electronic equipment (=donations). In the first stage I need to make 2 api calls: the first and the second are donations data and donors data (kept as different collections in mongodb) and then combine them. This info is shown in a donation route. The action looks like this:

const basicUrl = 'http://localhost:8000/api';
export const requestDonor_DonationData = () => getDonationData (
    `${basicUrl}/donor`, 
    `${basicUrl}/donation`
);

and the getDonationData func looks like this:

import {
    REQUEST_ENTITIES_PENDING,
    REQUEST_ENTITIES_SUCCES,
    REQUEST_ENTITIES_FAILED
} from './constants';
 

export const getDonationData = (urlDonor, urlDonation) => (dispatch) => {
    dispatch ( {type: REQUEST_ENTITIES_PENDING} );
    Promise.all([
        fetch(urlDonor).then(res => res.json()),
        fetch(urlDonation).then(res => res.json())
     ]).then ( ([ donorResult, donationResult]) => donorResult.data.map( (e, i) =>  Object.assign(e, donationResult.data[i]) ) )
        .then( mergedData => dispatch({type: REQUEST_ENTITIES_SUCCES, payload: mergedData }) )
        .catch(error => dispatch({type: REQUEST_ENTITIES_FAILED, payload: error}) ) 
}

that works fine.

In the second stage, When a donation have been peeked up, it become an equipment (not the perfect word..) which means that now it is waiting for inspection. this info is shown in a equipment route. the equipment data contain the donationId and status (different from the donation status). Now I want to do something similar:

  1. make 3 api calls (getting donor, donation, & equipment data)

  2. merging the donor whit its donation data

  3. filtering the merged data with the donations that have been peeked up (status='DONE')

  4. create a new json which takes the merged data and replace the ID and status of donation with the ID and status of the equipment.

I tried to do that with the first approach (just with Promise.all) but found it very confusing working with multiple ".then" ...

this is what I tried : the action-

export const requestEquipmentData = () => getEquipmentData ( 
    [
    `${basicUrl}/donor`, 
    `${basicUrl}/donation`,
    `${basicUrl}/equipment`
    ]
);

export const getEquipmentData = (urls) => (dispatch) => {
    dispatch ( {type: REQUEST_ENTITIES_PENDING} );
    try {
        const [ donorResult, donationResult, equipmentResult ] =  Promise.all(urls.map(async function(url) {
            const response = await fetch(url);
            return response.json();
        }));
        const donationInfo =  donorResult.data.map( (e, i) => Object.assign(e, donationResult.data[i]) );
        const filteredDonation = donationInfo.filter(item =>item.status==='DONE');
        const equipment =  filteredDonation.map( (donation,i) => {
                let obj = donation;
                obj.id = equipmentResult.data[i].id;
                obj.status = equipmentResult.data[i].status;   
                return obj;
        })
        dispatch({type: REQUEST_ENTITIES_SUCCES, payload: equipment });

    }  catch (error) {
        dispatch({type: REQUEST_ENTITIES_FAILED, payload: error})
    }
}

but I am doing somethig wrong, and that is the error:

type: "REQUEST_ENTITIES_FAILED", payload: TypeError: undefined is not a function

I would appreciate any help

Nathan Barel
  • 348
  • 2
  • 14
  • You may be calling `dispatch` in file you don't have access to it. Try to console log it and see. – Medi Apr 22 '21 at 12:05

1 Answers1

3

The result of Promise.all() is a Promise that resolves to the array of results. It is not an array itself so you cannot destructure it like this.

You can use the same .then() approach that you used in your first example:

export const getEquipmentData = (urls) => (dispatch) => {
    dispatch({ type: REQUEST_ENTITIES_PENDING });
    Promise.all(urls.map(async function (url) {
        const response = await fetch(url);
        return response.json();
    })).then(([donorResult, donationResult, equipmentResult]) => {
        const donationInfo = donorResult.data.map((e, i) => Object.assign(e, donationResult.data[i]));
        const filteredDonation = donationInfo.filter(item => item.status === 'DONE');
        const equipment = filteredDonation.map((donation, i) => {
            let obj = donation;
            obj.id = equipmentResult.data[i].id;
            obj.status = equipmentResult.data[i].status;
            return obj;
        })
        dispatch({ type: REQUEST_ENTITIES_SUCCES, payload: equipment });
    }).catch(error) {
        dispatch({ type: REQUEST_ENTITIES_FAILED, payload: error })
    }
}

Or you can use async/await syntax. Checkout this question for a generally discussion on resolving an array of Promises.

export const getEquipmentData = (urls) => async (dispatch) => {
    dispatch ( {type: REQUEST_ENTITIES_PENDING} );
    try {
        const [ donorResult, donationResult, equipmentResult ] =  await Promise.all(urls.map(async function(url) {
            const response = await fetch(url);
            return response.json();
        }));
        const donationInfo =  donorResult.data.map( (e, i) => Object.assign(e, donationResult.data[i]) );
        const filteredDonation = donationInfo.filter(item =>item.status==='DONE');
        const equipment =  filteredDonation.map( (donation,i) => {
                let obj = donation;
                obj.id = equipmentResult.data[i].id;
                obj.status = equipmentResult.data[i].status;   
                return obj;
        })
        dispatch({type: REQUEST_ENTITIES_SUCCES, payload: equipment });

    }  catch (error) {
        dispatch({type: REQUEST_ENTITIES_FAILED, payload: error})
    }
}

In my opinion your general approach here is not good. You should read the guides on Normalizing State Shape. It seems like your APIs are returning normalized data and then your are "unnormalizing" it by combining data from multiple endpoints.

Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
  • Thanks a lot ! I missed the async await with the second approach, and with the first approach I were not aware to that urls: string[]. you are right about the api's - it was not my decision to design them like this, the reason for that design as i been told is for reason of Producing reports, anyway I will explore it, thank again for the detailed answer!! – Nathan Barel Apr 22 '21 at 22:02
  • Oh! that `urls: string[]` is an accident. I write code in Typescript and I forgot to remove the type! – Linda Paiste Apr 22 '21 at 22:04