Nothing executes after forEach loop in redux - javascript

My redux code looks like following
const getCategories = (res) => (dispatch,getState) => {
let categories = []
const result = res.results
console.log("here")
result.forEach((rawMaterial,index)=>{
if(!_.includes(categories,rawMaterial.category[0].name)){
categories.push(rawMaterial.category[0].name)
dispatch({type:UPDATE_CATEGORIES,categories})
}
});
console.log("here2")
}
Basically it is a function which pushes data into categories array from input parameter "res". What my problem is, "getCategories" function does not executes anything after forEach loop.
The below piece of chunk does not get executed in the above code.
console.log("here2")
Function executes nothing after forEach loop.
Full code
export const getRawMaterials = (params = {}) => (
dispatch,
getState,
{ fetch }
) => {
dispatch({ type: GET_RAW_MATERIALS_REQUEST, params });
const { token } = dispatch(getToken());
const { search, ordering } = getState().rawMaterials;
return fetch(
`/pands/raw-materials/?${qs.stringify({
search,
ordering
})}`,
{
method: "GET",
token,
success: res => {
const rawMaterials = res.results
dispatch({ type: GET_RAW_MATERIALS_SUCCESS, res });
const categories = getCategories(rawMaterials);
dispatch({type:UPDATE_CATEGORIES,categories})
},
failure: err => dispatch({ type: GET_RAW_MATERIALS_FAILURE })
}
);
};
//doubt here, function get return after forEach
const getCategories = (res) => {
let categories = []
const result = res;
return result.map(rawMaterial => {
if(_.includes(categories,rawMaterial.category[0].name)) return null;
return rawMaterial.category[0].name;
}
)
}
My code is not working from the following line:
const categories = getCategories(rawMaterials);
Neither it is giving an error. I think problem is with response coming from backend.
"res" is coming from backend. my data is in "res.results". When I type check "res.results" , it shows object but when I print it, it shows array of objects.

Related

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:
make 3 api calls (getting donor, donation, & equipment data)
merging the donor whit its donation data
filtering the merged
data with the donations that have been peeked up (status='DONE')
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
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.

Unable to await Axios call multiple times

Can someone explain to me why I am unable to perform consecutive axios API calls this way? I figured that since each call was awaited, then both calls would simply be treated the same way.
When I run this code, I get the following error:
TypeError: parsedEmp is not iterable at Object.listEmployees
I do not receive the same error for the Axios call above it. Here's my code:
async function listEmployees() {
const { data } = await axios.get('https://gist.githubusercontent.com/graffixnyc/febcdd2ca91ddc685c163158ee126b4f/raw/c9494f59261f655a24019d3b94dab4db9346da6e/work.json')
const parsedData = data
emp_data = []
for(comp of parsedData) {
emp_data.push([comp['company_name'],comp['employees']])
}
const { data2 } = await axios.get('https://gist.githubusercontent.com/graffixnyc/31e9ef8b7d7caa742f56dc5f5649a57f/raw/43356c676c2cdc81f81ca77b2b7f7c5105b53d7f/people.json')
const parsedEmp = data2
rosters = []
for(entry of parsedEmp) {
if(entry['id'] == e) {
company.employees.push({ first_name: entry['first_name'], last_name: entry['last_name']})
}
}
return rosters
}
Using destructuring assignment requires you to use the property names present in the source object, otherwise the resulting variables will be undefined. The Axios response object does not have a data2 property.
You would need something like this to access the data property
const response2 = await axios.get('https://...')
const parsedEmp = response2.data
or you can use this format to name the assigned variable
const { data: data2 } = await axios.get(...)
const parsedEmp = data2
or even this to save an extra line each time
const { data: parsedData } = await axios.get(/* first URL */)
// ...
const { data: parsedEmp } = await axios.get(/* second URL */)
You can have the code like below
async function listEmployees() {
const { data: parsedData } = await axios.get('https://gist.githubusercontent.com/graffixnyc/febcdd2ca91ddc685c163158ee126b4f/raw/c9494f59261f655a24019d3b94dab4db9346da6e/work.json');
const { data: parsedEmp } = await axios.get('https://gist.githubusercontent.com/graffixnyc/31e9ef8b7d7caa742f56dc5f5649a57f/raw/43356c676c2cdc81f81ca77b2b7f7c5105b53d7f/people.json');
// get the employee details
let emp_data = [];
_.map(parsedData, (data) => {
emp_data.push(data.company_name, data.employees);
});
// get the roster data
let rosters = [];
_.map(parsedEmp, (data) => {
roster.push({ first_name: data['first_name'], last_name: data['last_name']});
});
return rosters;
}

Array of strings getting converted to Objects

I'm pushing files to amazon using pre-signed URLs, and modifying the files array with the file name reference inside the newData object. (The files array are inside an array of objects called items)
// Add job
const addJob = async(data, user) => {
const newData = { ...data };
data.items.map((item, itemIndex) => {
if (item.files !== []) {
item.files.map(async(file, fileIndex) => {
const uploadConfig = await axios.get(`/api/s3upload`, {
params: {
name: file.name,
},
});
console.log(uploadConfig.data.key);
newData.items[itemIndex].files[fileIndex] = uploadConfig.data.key;
await axios.put(uploadConfig.data.url, file);
});
}
});
console.log(newData);
try {
const res = await axios.post('/api/jobs', newData);
dispatch({
type: ADD_JOB,
payload: res.data,
});
} catch (error) {
console.log(error);
}
};
The file references comes in the uploadConfig.data.key and are being save into the newData object.
When this function is executed, something peculiar happens:
the console log of newData returns the correct array of references to the files
the files are uploaded just fine
the request made to /api/jobs, which is passing newData, sends an array of objects that contains { path: ... }
console.log(newData):
Post request:
JavaScript does this because forEach and map are not promise-aware. It cannot support async and await. You cannot use await in forEach or map.
for loops are promise-aware, thus replacing the loops with for loops and marking them as await returns the expected behaviour.
source: zellwk article
Corrected (functioning) code:
const addJob = async (data, user) => {
const newData = { ...data };
const { items } = data;
const loop = async () => {
for (let outer in items) {
if (items[outer].files !== []) {
const loop2 = async () => {
for (let inner in items[outer].files) {
const uploadConfig = await axios.get(`/api/s3upload`, {
params: {
name: items[outer].files[inner].name,
},
});
const res = await axios.put(uploadConfig.data.url, items[outer].files[inner])
newData.items[outer].files[inner] = uploadConfig.data.key;
}
};
await loop2();
}
}
};
await loop();
try {
const res = await axios.post('/api/jobs', newData);
dispatch({
type: ADD_JOB,
payload: res.data,
});
} catch (error) {
console.log(error);
}
};

AsyncStorage.getItem returns undefined(even with .then and .parse)

I'm attempting to store data in AsyncStorage and load them back(obviously). The .setItem function works, and the notification pops up at the bottom of the iOS simulator when I call it. However, the .getItem function doesn't work, and when I console.log it, returns undefined. I have two functions to store and fetch the data:
setData = (rawDataToStore, keyToStore) => {
data_store = JSON.stringify(rawDataToStore);
AsyncStorage.setItem(keyToStore, data_store, () => {
console.warn('Stored data!')
} )
}
getData = (keyToSearch) => {
AsyncStorage.getItem(keyToSearch).then(storage => {
parsed_data = JSON.parse(storage);
return parsed_data
}).catch(e => console.warn(e))
}
I just tested the functions in my render():
to save the data:
this.setData({value: 1}, "test_data");
to load the data:
console.log(this.getData("test_data"));
The console.log just returns undefined.
I'm completely new to Asyncstorage, but what am I doing wrong?
Your console.log returns undefined ... cause, your setData function hasn't finished its job yet ... you have to await for it first, cause it's an async operation.
class YourComponent extends React.Component {
async componentDidMount() {
await this.setData();
const data = await this.getData();
console.log('data returned', data);
}
setData = async (rawDataToStore, keyToStore) => {
data_store = JSON.stringify(rawDataToStore);
await AsyncStorage.setItem(keyToStore, data_store);
};
getData = async keyToSearch => {
let parsed_data = null;
const storage = await AsyncStorage.getItem(keyToSearch);
if (storage) {
parsed_data = JSON.parse(storage);
console.log('Data in AsyncStorage: ', parsed_data);
}
return parsed_data;
};
}

How to use Fetch queries in a loop?

I make a request to the server via a map with different urls, then I set the data in State and use it for output. I want the requests to be consecutive but sometimes they do not work correctly and get bugs, how to write the code for normal data retrieval?
const urlList = ["countries", "states", "cities", "users"];
componentDidMount() {
urlList.map( (url, index) => {
return servicesAPI.getResourse(url).then( (body) => {
index !== 3 ? this.setState({
dataAPI : [...this.state.dataAPI, body] }) :
this.setState({
dataAPI : [...this.state.dataAPI, body],
loaded: true
})
})
})
export default class ServicesAPI {
_apiBase = `http://localhost:3001/`;
async getResourse(url) {
const res = await fetch(`${this._apiBase}${url}`);
if (!res.ok) {
throw new Error(`Could not fetch ${url}` +
`, received ${res.status}`)
}
return await res.json();
}
Use of Promise.all();
componentDidMount() {
const fetchPromises = [];
urlList.forEach( (url, index) => {
fetchPromises.push(servicesAPI.getResourse(url));
});
const allResourcesPromise = Promise.all(fetchPromises);
allResourcesPromise.then(data => {
// list with responses
}).catch(err => {
console.log(err.toString());
});
}
Sample example:
https://jsbin.com/vevufumano/1/edit?html,js,console,output
Also instead of then, where is possible, you can use async/await for more cleaner code.

Categories