Search code examples
javascriptreactjsreduxes6-promise

Handling multiple api calls with Promis.all and manipulating the data with react-redux


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


Solution

  • 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.