Grabbing the Value of Path in Firebase With Promises - javascript

I want to grab the value of a path in Firebase. Say I do
firebaseAuth.onAuthStateChanged(user => {
var displayName =
firebase.database()
.ref('/users/' + user.uid + '/name/')
.once('value').then(snapshot => snapshot.val());
store.dispatch('checkUser', {
user
displayName
});
});
In my console I get a promise as the value of my users displayName, inside of that I see the correct value, but how do I get to it? It throws me an error when I try and set displayName to snapshot.val() in the callback.
Why can't I do this? Seems like in the documentation it's possible.

The error is thrown because the syntax is incorrect; it would need to be enclosed in a block:
.once('value').then(snapshot => { const displayName = snapshot.val(); });
However, that won't solve the problem. You need to move the dispatch call into the callback - where the snapshot has been resolved:
firebaseAuth.onAuthStateChanged(user => {
firebase.database()
.ref('/users/' + user.uid + '/name/')
.once('value').then(snapshot => store.dispatch('checkUser', {
user
displayName: snapshot.val()
}));
});

Looks like a syntax problem. Arrow functions have a few valid configurations with brackets etc. that can be tough to sort out (it still bites me sometimes after a year of using ES6).
Here's a few valid versions to consider. This isn't an exhaustive list of possible syntax combinations, but I find it covers most of my use cases:
Standard form, multi line block, brackets. This is the version you use when you have more to do than just return a value (e.g., side effects like dispatch):
(snapshot) => {
const displayname = snapshot. val()
// then you'd dispatch here
return
}
One liner, term after the arrow is assumed to be return value. No assignments on the right side in this version, and no dispatch calls:
snapshot => snapshot.val()
In-between, multiple lines enclosed by parentheses, but still just one value assumed to be return value:
snapshot => ({
displayName: snapshot.val()
})
// returns { displayName: "foo" }

Related

Throwing an error TypeError: Cannot add property 0, object is not extensible

Please see the code below for reference.
export const listenToHotels = (hotelIds: string[]): Observable < Hotel[] > => {
return new Observable < Hotel[] > ((observer) => {
const hotels: any = [];
hotelIds.forEach((hotelId) => {
let roomingList: any;
return FirestoreCollectionReference.Hotels()
.doc(hotelId)
.onSnapshot(
(doc) => {
roomingList = {
hotelId: doc.id,
...doc.data()
}
as Hotel;
console.log(`roomingList`, roomingList);
hotels.push({
...roomingList
});
},
(error) => observer.error(error)
);
});
//Check for error handling
observer.next(hotels);
console.log('hotels', hotels);
});
};
As you can see I am trying to run a forEach on a hotelId Array and in that firestore listener is being executed. Now I want to save the response and push that into hotels array but it gives me an error object not extensible error.
The thing is observer and console.log('hotels',hotels) run first because of promise being executed at later stage.
Please let me know how can I resolve this issue.
I think you could use map instead of forEach, because you could use await with map
example of using map and await: https://flaviocopes.com/javascript-async-await-array-map/
Check out forkJoin. It takes an array of Observables and subscribes to them at the same time.
This example is written in Angular but the operators should be identical as long as you're using a current version of rxjs.
I'm starting with an array of User objects (users.mock.ts)
Each Object is then map to a single Observable (mapCalls)
I now have an array of Observables that will make HTTP calls. These calls will not be made until subscribed to
forkJoin is then added as a wrapper to the array
You subscribe to that forkJoin. All of the objects will make the call.
When the calls have completed, the logic inside of subscribe() will be run
So in your case:
Map the Hotel Ids to a variable calls. Each item is the Observable logic you posted
You would then run forkJoin(calls).subscribe() to make all the calls
export const listenToHotels = (hotelIds: string[]): Observable<Hotel[]> => {
const hotelsObservable = hotelIds.map((hotelId) => {
let roomingList: any;
return new Observable<Hotel>((observerInside) => {
FirestoreCollectionReference.Hotels()
.doc(hotelId)
.onSnapshot(
(doc) => {
roomingList = { hotelId: doc.id, ...doc.data() } as Hotel;
observerInside.next(roomingList);
},
(error) => observerInside.error(error)
);
});
});
const combinedObservable = combineLatest(hotelsObservable);
return combinedObservable;
//Check for error handling
};
The issue was how I was handling the chained observables(Execution of promise is delayed than a normal code because they will be put in micro-queue first). They need to be handled using zip or combineLatest but latter is much better in this use case as we need the latest values for the observables.

get data from array of objects containg promises

In my react app I have some user ids and need to fetch their emails and names from different endpoints so what i do is:
const promises = ids.map(
id => ( {email: axios.get(`blabla/${id}/email`), name: axios.get(`blabla/${id}/name`)} )
);
and it gets me back:
[
{email: Promise, name: Promise},{email: Promise, name: Promise},{email: Promise, name: Promise},...
]
Now to get the data what i do is:
const results = [];
promises.map(({ email, name}) =>
Promise.all([email, name]).then((result) => {
results.push({
email: result[0].data,
name: result[1].data,
});
})
);
but i have a feeling that it may not be a good way to it, i mean it works now but i don't want to get into any issues later !! haha, for instance, a race between promises, or for instance, a user email set for another user. I don't know if they are even possible to happen but I need to check with you experts that if you confirm this way or do you suggest something else.
It's fine other than that you're not using the array map creates. Any time you're not using map's return value, use a loop (or forEach) instead. Also, any time you're writing to an array you're closing over from asynchronous operation results, it sets you up to accidentally use that array before it's populated
In this case, though, I'd use the array map gives back:
Promise.all(
promises.map(({ email, name}) =>
Promise.all([email, name])
.then(([email, name]) => ({email, name}))
)
).then(results => {
// ...use results here, it's an array of {email, name} objects
})
.catch(error => {
// ...handle error here, at least one operation failed...
});
I'd be doing it like this.
const promises = ids.map(
id => ( [axios.get(`blabla/${id}/email`), axios.get(`blabla/${id}/name`)] )
);
Promise.all(promises.map(Promise.all).then(
([email, name]) => {
console.log(email, name)
}
)).then(values => {
console.log(values);
});

Cloud Function queries Firestore but returns empty array

So I am building a cloud function, which accepts a customerID, filters documents based on the customerID and returns the list of documents. Sadly it returns an empty array. I'm sure this is a simple fix.
Here is the cloud function:
export const getCalendarItems = functions.https.onCall(async (data, context) => {
const uid = data.uid;
if (context.auth) {
let array = [{}];
const ref = admin.firestore().collection("photoshoots");
const query = ref.where("customerID", "==", uid);
query.onSnapshot((querySnapshot) => {
querySnapshot.docs.forEach((documentSnapshot) => {
array.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
});
return array;
} else {
return false;
}
});
Here is the code when I call it from the client side.
const uid = auth().currentUser.uid;
functions()
.httpsCallable("getCalendarItems")({
uid: uid
})
.then(result => {
console.log(result.data);
});
Also here is a screen shot of Firestore.
Screenshot of firestore
There are a couple things wrong with your function.
First, you shouldn't use onSnapshot() to query in Cloud Functions. Use get() instead to perform a query a single time. onSnapshot() attaches a listener to receives updates in real time, and that's not what you want here.
Second, your function needs to return a promise that resolves with the data to send to the client. get() returns a promise with a QuerySnapshot object. You will need to use that to get the list of documents that matched your query. You can use that list to send back results to the client.
The function code you want will look significantly different than what you have now, after you make the recommended changes.

Return posted data with res.send after post request?

I have a post route that receives an array of strings from req.body. It then takes the array and queries my local MySQL database a few times and retrieves some data that I am wanting to send back to the client (react) and then store in state.
The problem I'm having is storing my data in an array to successfully send via the res.send or res.json method.
I believe it may be an issue solved with handling the scope in another manner but I am stumped on this one.
I've tried moving the location of the original array variable declaration, but at best with res.send(aisleArr), I receive only an empty array.
I also tried not declaring 'aisleArr' before the push method, thinking that it would create a global aisleArr object but to no avail.
router.post('/complete', (req, res) => {
const list = req.body;
let aisleArr = [];
list.map(item =>
db.item.findAll({
where: {'$item.name$': item}
})
.then(data => data.map( data =>
db.aisle.findAll({
where: {'$aisle.id$': data.dataValues.aisleId, '$aisle.storeId$': 2}
}).then( result => {
if(result[0]){
aisleArr.push(result[0].name)
}else{
console.log('no match')}})
)
)
)
res.send(aisleArr)
});
In the client console only an empty array is received upon res.send completing.
It looks like you may need to make sure the map iterator is complete before you send the value. Even though the return values are chained with promises, the assignment to aisleArr is not and res.send is probably firing before the iterator is finished. Using async/await would provide an easier way to ensure that assignment is complete before moving forward.
For example:
router.post('/complete', async (req, res) => {
const list = req.body;
const aisleArr = await list.map(item =>
db.item.findAll({
where: {'$item.name$': item}
})
.then(data =>
data.map(newData =>
db.aisle.findAll({
where: {'$aisle.id$': newData.dataValues.aisleId, '$aisle.storeId$': 2}
})
.then(result => {
if (result[0].name) {
return result[0].name;
} else {
console.log('no match');
}
});
);
);
).filter(aisle => aisle);
// the filter at the end will remove null values
// (when nothing is returned from the last call to .then)
res.send(aisleArr);
});
I did not run this so it may take a tweak or two, but I hope it's a good starting point. Overall, I think the main thing to watch out for is that the map is completed before you call res.send. It may be helpful to add some more console.logs to ensure that this is the issue.

Axios AJAX call in React returning only initial state

I'm trying to make a simple AJAX call using Axios in React, but I can't figure out where I'm going wrong.
Here is what I have so far (within my component):
ComponentDidMount() {
axios.get('https://jsonplaceholder.typicode.com/users')
.then( res => {
const users = res
this.setState({ users });
});
}
render() {
console.log(this.state.users) // returns the **initialised EMPTY array**
return (
<div>
{this.state.users.map( user => <p>{user.name}</p>)} // returns nothing
</div>
)
}
I am accessing a simple array at the URL given in the .get() method (check it out to see it's structure if necessary). I then chain on the .then() promise and pass the res argument in an arrow function.
Then I assign the users variable to the result, and attempt to set the state as this. So at this point I'm expecting this.state.users to equal the resulting array.
However...
A) When I try console.log(this.state.users), it returns the initialised empty array
B) When I try to render a map of the array, iterating through the name property on each object, again, nothing.
Where am I going wrong?
A part from the typo (as Phi Nguyen noted), a few other things to fix:
1) Axios resolves the promise with a response object. So to get the results you need to do:
axios.get('https://jsonplaceholder.typicode.com/users')
.then( res => {
const users = res.data; // Assuming the response body contains the array
this.setState({ users });
});
Check out the docs about the response object here: https://github.com/mzabriskie/axios#response-schema
2) If there's an exception (your request fails) you probably want to handle it instead of swallowing it. So:
axios.get('https://jsonplaceholder.typicode.com/users')
.then( res => {
const users = res.data; // Assuming the response body contains the array
this.setState({ users });
})
.catch(err => {
// Handle the error here. E.g. use this.setState() to display an error msg.
})
typo. It should be componentDidMount instead of ComponentDidMount.

Categories

Resources