Getting null in values from Promise.all - javascript

I am using promises. This is in continuation to my question here
The issue I am having is that in response, i.e. an array of objects is having null values. I will try to explain this
First I get the userId
Get user whishlist products from the userId
Then using userId I get stores/shop list
Then I iterate over store list and call another API to check if this store is user favourite store.
Then I get the products of each store and append in an object and return.
function getStoresList(context) {
const userID = common.getUserID(context)
let userWishListProd = []
return userID
.then(uid => Wishlist.getUserWishlistProducts(uid).then((products) => {
userWishListProd = products.data.map(product => +product.id)
return uid
}))
.then(uid => api.getOfficialStoresList(uid).then((response) => {
if (!response.data) {
const raw = JSON.stringify(response)
return []
}
const shops = response.data
return Promise.all(
shops.map((shop) => {
const id = shop.shop_id
const shopobj = {
id,
name: shop.shop_name,
}
return favAPI.checkFavourite(uid, id)
.then((favData) => {
shopobj.is_fave_shop = favData
// Fetch the products of shop
return getProductList(id, uid)
.then((responsedata) => {
shopobj.products = responsedata.data.products.map(product => ({
id: product.id,
name: product.name,
is_wishlist: userWishListProd.indexOf(product.id) > -1,
}))
return shopobj
})
.catch(() => {})
})
.catch(err => console.error(err))
}))
.then(responses => responses)
.catch(err => console.log(err))
})
.catch(() => {}))
.catch()
}
The response I get is
[{
"id": 1001,
"name": "Gaurdian Store",
"is_fave_shop": "0",
"products": [{
"id": 14285912,
"name": "Sofra Cream",
"is_wishlist": false
}]
},
null,
null,
{
"id": 1002,
"name": "decolite",
"is_fave_shop": "1",
"products": [{
"id": 14285912,
"name": "Denca SPF",
"is_wishlist": false
}]
}
]
The actual store are coming as 4 but instead of it null gets appended. What is wrong I am doing with Promises here.

This appears to have to do with your .catch(() => {}) and .catch(err => console.error(err)) invocations. If one promise in your loop has an error, it will be transformed to an undefined value (optionally being reported before), so that Promise.all will fulfill with an array that might contain undefined values. If you JSON.stringify that, you'll get null at those indices.
Drop the .catch(() => {}) statements that do nothing (or replace them with logging ones), and check your error logs.

Have you debug your code?
I would debug on Chrome and add some break points on the code to see what the actual response is.

Related

Compare two arrays in react and filter one based on matches of name property

I'm trying to filter a players array by comparing the names property to a roster array and rendering the matches from the players array.
When a user selects a year the getCollegeRoster() function returns players who have a year property matching the selection. From there I'm trying to filter and display the .name matches from the players array, but it seems i'm unable to update playerStateValue. I'm using recoil to manage state. Any insight would be much appreciated.
const getCollegeRoster = async () => {
setLoading(true);
try {
const playersQuery = query(
collection(firestore, `colleges/${communityData.id}/rosters`),
where("year", "==", selection),
);
const playerDocs = await getDocs(playersQuery);
const collegeRoster = playerDocs.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
setRoster(collegeRoster as unknown as Roster[]);
console.log('collegePlayers', roster);
} catch (error: any) {
console.log("getCollegePlayers error", error.message);
}
setLoading(false);
};
const onSelection = (newSelection: any) => {
setSelection(newSelection);
console.log('newselect', newSelection)
getCollegeRoster();
const filteredArray = [...playerStateValue.players].filter((el) => ({
name: el.name,
match: [...roster].some((f) => f.name === el.name)
})
);
setPlayerStateValue((prev) => ({
...prev,
players: filteredArray as Player[],
playersCache: {
...prev.playersCache,
[communityData?.id!]: filteredArray as Player[],
},
playerUpdateRequired: false,
}));
} ```
also tried adding setplayerstatevalue into the getcollegeroster function:
onst getCollegeRoster = async () => {
console.log("WE ARE GETTING Players!!!");
console.log('communitydata', communityData);
setLoading(true);
try {
const playersQuery = query(
collection(firestore, `colleges/${communityData.id}/rosters`),
where("year", "==", selection),
);
const playerDocs = await getDocs(playersQuery);
const collegeRoster = playerDocs.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
setRoster(collegeRoster as unknown as Roster[]);
console.log('collegePlayers', roster);
const filteredArray = [...playerStateValue.players].filter((el) => ({
name: el.name,
match: [...roster].some((f) => f.name === el.name)
})
);
setPlayerStateValue((prev) => ({
...prev,
players: filteredArray as Player[],
playersCache: {
...prev.playersCache,
[communityData?.id!]: filteredArray as Player[],
},
playerUpdateRequired: false,
}));
} catch (error: any) {
console.log("getCollegePlayers error", error.message);
}
setLoading(false);
};
{playerStateValue.players.map((player: Player, index) => (
<PlayerItem
key={player.id}
player={player}
// postIdx={index}
onPlayerVote={onPlayerVote}
userVoteValue={
playerStateValue.playerVotes.find((item) => item.playerId === player.id)
?.voteValue
}
onSelectPlayer={onSelectPlayer}
/>
I believe I found the problem. getCollegeRoster() is an async function which means it is being executed asynchronous.
That function is responsible for updating roster and you are using roster inside your filtering while it is being asynchronously updated.
There is no guarantee that roster is updated by the time you filter your array.
You have two ways of solving this. Either you execute setPlayerStateValue() inside of getCollegeRoster() or you wait for getCollegeRoster() to be done by preappending it with await and your onSelection function with async:
const onSelection = async (newSelection: any) => {
setSelection(newSelection);
console.log("newselect", newSelection);
await getCollegeRoster();
const filteredArray = [...playerStateValue.players].filter((el) => ({
name: el.name,
match: [...roster].some((f) => f.name === el.name),
}));
...
Edit:
Another scenario I can think of is that playerStateValue might be the culprit. In React setState works asynchronous as well, if you access playerStateValue via curr/prev-callback, it is guaranteed that you are accessing the values of the current state object while updating the state. Try this use of setPlayerStateValue:
setPlayerStateValue((prev) => {
const filteredArray = prev.players.filter((el) => ({
name: el.name,
match: [...roster].some((f) => f.name === el.name),
})) as Player[];
return {
...prev,
players: filteredArray,
playersCache: {
...prev.playersCache,
[communityData?.id!]: filteredArray,
},
playerUpdateRequired: false,
};
});

forEach objects value equals undefined

Data Received from firebase-realtime-database as following
{
"0": {
"id": 10250903,
...
},
"1": {
"id": 10810490,
...
},
...
}
Code to setProducts
const [products, setProducts] = useState();
const {isLoading, setIsLoading} = useData();
useEffect(async () => {
setIsLoading(true);
await firebase
.database()
.ref('events')
.once('value')
.then((events) => {
setProducts(events);
})
.finally(() => setIsLoading(false));
}, []);
I tried to iterate the object to get the values
products?.forEach((product) => {
console.log(product);
});
Result:
Object {
"id": 10250903,
...
}
Object {
"id": 10810490,
...
}
But when I try to access id values, console prints undefined
products?.forEach((product) => {
console.log(product.id); // undefined
});
undefined
undefined
I am stuck I tried everything.
Object.values(products) will not work since product will be undefined until data is received.
Creating a new Array and mapping into it also will not work.
You stated that:
Object.values(products) will not work since product will be undefined until data is received.
I think you are very close to the solution.
Using products || {} handles the case where products are undefined or null.
var products = {
"0": {
"id": 10250903,
},
"1": {
"id": 10810490,
},
}
Object.values(products || {}).forEach(p => console.log(p.id))
If you are transforming products into a new products collection, reduce may become useful:
Object.values(products || {}).reduce((acc, p) => {
acc[p.id] = p;
return acc;
}, {})
=>
{
"10250903": {
"id": 10250903,
...
},
"10810490": {
"id": 10810490,
...
}
}
Or:
Object.values(products || {}).reduce((acc, p) => {
acc.push(p.id)
return acc;
}, [])
=>
[10250903, 10810490]

How can I retrieve elements and properties from the given JSON

How can I access each of the properties in the given JSON?
I would also like to filter the particular objects based on their IDs. How to do that using array.filter method in javascript?
{
"records": [
{
"id": "abcd",
"fields": {
"term": [
"xyz"
],
"groupId": 888,
"url": "https://www.facebook.com",
"url_c": [
"https://www.google.com"
],
"pID": [
"1800"
],
"location": [
"mumbai"
],
"url_location": [
"https://www.mumbai.com/"
]
},
"createdTime": "2021-05-12T10:18:33.000Z"
}
]
}
Currently I'm trying to do this:
const [info, setInfo] = useState({});
useEffect(() => {
fetch(
"https://apiurl//"
)
.then((res) => res.json())
.then((data) => {
setInfo(data.records);
console.log(data);
})
.catch((error) => {
console.log(error);
});
}, []);
let resultInfo = info.filter((x) => x.groupId == gid);
console.log(resultInfo[0].fields.groupId);
But it shows error
You're initializing your info as an empty object. So your code is trying to run filter on an object which will return an error. Also in your filter check is wrong based on the json example you shared
You should change
const [info, setInfo] = useState({});
let resultInfo = info.filter((x) => x.groupId == gid);
to
const [info, setInfo] = useState([]);
let resultInfo = info.filter((x) => x.fields.groupId == gid);
Since it's asynchronous either define things in .then() method or you can simply use asyn await to let things work for you.
Something like
const [info, setInfo] = useState([]);
useEffect(() => {
fetch(
"https://apiurl//"
)
.then((res) => res.json())
.then((data) => {
if(data && data.records){
console.log(data);
const records = data.records.filter((x) => x.fields.groupId == gid);
setInfo(records);
// if you will log info here most probably it might log default value so check in your render method for the latest value
}
})
.catch((error) => {
console.log(error);
});
}, []);
Make sure gid is same as your groupId. For surity you can just pass static number just to test.
Now try to access info in your render method like:
return
<div>
{JSON.stringify(info)}
</div>

how to make nested bitbucket API http request in ngrx effects

BitBucket API 1.0 returns tags with no timestamp. Example response:
{
"size": 2,
"limit": 2,
"isLastPage": false,
"values": [
{
"id": "refs/tags/tag1",
"displayId": "tag1",
"type": "TAG",
"latestCommit": "ff7be6fad2e660e8139f410d2585f6b2c9867a61",
"latestChangeset": "ff7be6fad2e660e8139f410d2585f6b2c9867a61",
"hash": "f13db8e5c0b75b57b48777299d820525ad8127b9"
},
{
"id": "refs/tags/tag2",
"displayId": "tag2",
"type": "TAG",
"latestCommit": "4f5878c9554444755dbf6699eac33ff8752add5f",
"latestChangeset": "4f5878c9554444755dbf6699eac33ff8752add5f",
"hash": "23274bd5c9b87614f14a2245d5e70812c83104b7"
}
]
}
Therefore I am trying to request the latestCommit to get it's data. The response retrieves the committerTimestamp which I want to add to the tag object.
The ngrx effect and a following function are written as follows:
#Effect()
loadTags: Observable<LoadTagsRes> = this.actions$.pipe(
ofType(BitbucketActionTypes.LOAD_TAGS_REQ),
switchMap(() => {
return this.http.get('https://bitbucket/rest/api/1.0/projects/projects/repos/project/tags?limit=2', httpOptions).pipe(
map((response: any) => response.values.map(tag => (
{
...tag,
timeStamp: this.getTagTimestamp(tag.latestCommit)
}
))
),
map((response: any) => new LoadTagsRes({tags: response}))
)
}),
);
getTagTimestamp(tag) {
return this.http.get<any>(`https://bitbucket/rest/api/1.0/projects/projects/repos/project/commits/${tag}`, httpOptions).pipe(
switchMap(res => {
return res;
})
);
}
the new timeStamp property in the array of objects displays the following in redux devtools:
Would like to get the correct response for the inner http request.
The function should trigger the secondary request and fetch the timestamp but it isn't being triggered yet. You need to use switchMap (or any other RxJS higher order mapping operator) to trigger it. And seeing that you have multiple requests, you could use RxJS forkJoin function to trigger them in parallel.
Try the following
#Effect()
loadTags: Observable<LoadTagsRes>= this.actions$.pipe(
ofType(BitbucketActionTypes.LOAD_TAGS_REQ),
switchMap(() => {
return this.http.get('https://bitbucket/rest/api/1.0/projects/projects/repos/project/tags?limit=2', httpOptions).pipe(
switchMap((response: any) => {
forkJoin(response.values.map(tag =>
this.getTagTimestamp(tag.latestCommit).pipe(
map(timeStamp => ({...tag, timeStamp: timeStamp}))
)
))
}),
map((response: any) => new LoadTagsRes({
tags: response
}))
)
}),
);
loadTags: Observable<LoadTagsRes> = this.actions$.pipe(
ofType(BitbucketActionTypes.LOAD_TAGS_REQ),
switchMap(() => {
return this.http.get('https://bitbucket/rest/api/1.0/projects/projects/repos/project/tags?limit=2', httpOptions).pipe(
mergeMap((response: any) => response.values.map(tag => {
// array of observables
const tag$=this.getTagTimestamp(tag.latestCommit);
// use forkJoin to return single observable that returns array of results
return forkJoin(tag$)
})
),
map((response: any) => new LoadTagsRes({tags: response}))
)
}),
);
getTagTimestamp(tag) {
return this.http.get<any>(`https://bitbucket/rest/api/1.0/projects/projects/repos/project/commits/${tag}`, httpOptions).pipe(
switchMap(res => {
return res;
})
);

How can I refactor this setState by looping through the object?

I feel like this should be much easier than I'm finding it...
Right now, I have this code, and it works:
componentDidMount() {
fetch(API_URL + `/interviews/general/${this.props.employeeId}`)
.then((res) => {
if (!res.ok) {
throw new Error();
}
return res.json();
})
.then((result) => {
this.setState({
organization: result[0].organization,
address: result[0].address,
website: result[0].website,
industry: result[0].industry,
co_status: result[0].co_status,
mission: result[0].mission,
});
console.log(result);
})
.catch((error) => {
console.log(error);
});
}
The issue is that this is actually data that I'm retrieving and then displaying the results in a form. BUt this is just part of it. There are actually closer to 50 key value pairs in this object. There has to be a way from me to loop through that setState, right?
This is what "result" looks like, which is what my fetch returns:
[{…}]
0:
COBRA_admin: ""
address: "1914 Skillman St, Suite 110153, Dallas, TX 75206"
benefits_broker: ""
co_status: "Private"
created_at: "2020-09-29T01:54:48.000Z"
employee_id: 104
general_id: 24
headcount_consultants: 0
headcount_fulltime: 0
headcount_nonexempt: 0
headcount_parttime: 0
headcount_temp: 0
hrlead_email: ""
hrlead_name: ""
hrlead_phone: ""
hrlead_title: ""
industry: "HR Consulting"
locations: ""
locations_headcount: ""
mission: "TO go and do great things. "
organization: "People Performance Resources LLC"
pointofcontact_email: ""
pointofcontact_name: ""
pointofcontact_phone: ""
pointofcontact_title: ""
retirement_admin: ""
seniorlead_email: ""
seniorlead_name: ""
seniorlead_phone: ""
seniorlead_title: ""
updated_at: "2020-09-30T20:47:39.000Z"
website: "www.pprhr.com"
If you want all the keys, just do:
this.setState({...result[0]});
Use a map function to loop the data and return the values in a state variable that will maintain the values for the entire results array for a specific key.
componentDidMount() {
fetch(API_URL + `/interviews/general/${this.props.employeeId}`)
.then((res) => {
if (!res.ok) {
throw new Error();
}
return res.json();
})
.then((result) => {
this.setState({
organizations: result.map(item => item.organization),
addresses: result.map(item => item.address),
websites: result.map(item => item.website),
industries: result.map(item => item.industry),
co_status: result.map(item => item.co_status),
missions: result.map(item => item.mission),
});
console.log(result);
})
.catch((error) => {
console.log(error);
});
}
To display the data
// pass missions as a prop
const App = ({ missions }) => {
return (
<ul> {missions.map(mission => <li> {mission} </li>)} </ul>
)
}
Would you mind considering deconstruction statement, beside with async/await, please look into pseudocode:
async componentDidMount() {
try {
let response = await fetch( <url> );
if (!response.ok) {
throw Error(response.statusText);
}
const {
organization, address, website, industry, co_status, mission
} = response.json(); // deconstruction
this.setState((prevState) => ({
...prevState,
organization,
address,
website,
industry,
co_status,
mission
}));
} catch (error) {
throw Error(error.message);
}
}
However those functionalities covers same functionality, please be aware that those statements are not equal. Please take a look to get know what is a difference async/await vs Promisses, keyword is stack trace.
If you are looking for some good must know fundamentals, please visit:
JavaScript to Know for React by Kent C. Dodds

Categories

Resources