My function is being called before the state is changed - javascript

I'm working in Reactjs and my database is Firebase.
I'm trying to retrieve data from my database and then checking data regarding it.
At the top, I have const [group, setGroup] = useState(null); for my group data. In my CheckGroupRelationship function, if checks the database using the group object data.
Initially I had the function called after the database call:
const GetGroupInfo = async (uid) => {
await props.firestore
.collection("groups")
.doc(uid)
.get()
.then(async (doc) => {
if (!doc.exists) {
console.log("No such document!");
} else {
setGroup({
id: doc.data().id,
ownerID: doc.data().ownerID,
name: doc.data().name,
});
}
})
.catch((err) => {
console.log("Error getting document", err);
});
CheckGroupRelationship();
};
However, I would get the error "Group is null". So I moved the function called to the setState:
const GetGroupInfo = async (id) => {
await props.firestore
.collection("groups")
.doc(id)
.get()
.then(async (doc) => {
if (!doc.exists) {
console.log("No such document!");
} else {
setGroup(
{
id: doc.data().id,
ownerID: doc.data().ownerID,
name: doc.data().name,
bio: doc.data().bio,
},
CheckGroupRelationship()
);
}
})
.catch((err) => {
console.log("Error getting document", err);
});
};
This would still result in the "group is null" error. I'm not sure what else I can do to get the function to be called AFTER the state is set.

the easy solution would be to use useEffect. In your case you can do this:
const [group, setGroup] = useState(null);
useEffect(() => {
if (group) {
CheckGroupRelationship()
}
}, [group])
What useEffect does here is waiting for group to change and when it changes it calls the inside callback. In this case you will have group updated there. Hope it helps, cheers

Related

FIREBASE getting document data

I'm making an app and trying to get product data by it's id inside a modal in ionic 4.
I'm using typescript to do it but without luck.
Because de calls to firebase are asynchronous i cannot get the data that is held in firebase and also because i'm new to subject i cannot figured out the proper way to write the code.
I read about how to do it but i'm having a hard time to achieve it.
Here is my function that tries to grab product data from firebase.
It always logs empty on console.log('todo', todo).
async editProduct(id) {
const getTodo = docRef => {
setTimeout(() => {
docRef = this.afs.collection("products").doc(id);
docRef.get().subscribe((doc) => {
if (doc.exists) {
let data = doc.data();
return data;
} else {
console.log("No document.");
return false;
}
});
}, 2000)
}
getTodo(todo => {
console.log('todo', todo)
})
const modal = await this.modalCtrl.create({
component: AdminProductPage,
'id': id,
});
await modal.present();
}
There is something wrong with your "getTodo". Probable you are logging empty data with your code, I can give you the proper functional example:
myData
editProduct() {
this.afs.collection("products").doc(id)
.valueChanges()
.subscribe(data => {
console.log(data)
myData = data
})
}
getData() {
console.log(this.myData) // You will log it twice with this line
}
GOOGLE EXAMPLE
docRef.get().then((doc) => {
if (doc.exists) {
console.log("Document data:", doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch((error) => {
console.log("Error getting document:", error);
});
https://firebase.google.com/docs/firestore/query-data/get-data?hl=es

How to modify and store data, retrieved from an API call, in Firestore correctly

I retrieve data from an API and want to add new properties(comments:[], likes:'', etc.) each of the map. What I tried is that modifying data on UI side, and it didn't effect Firestore side. Even I modify the data on Firestore side by creating new collection it works until I refresh the app. Can you please show me the correct way how to modify and store the data?
const newsFromApiRef = firestore().collection('topHeadlines');
//Adding data to firestore
const newsOnFirebase = async () => {
await getTopHeadlines('us')
.then((data) => {
newsFromApiRef
.doc('testDocUID')
.set(data)
.then(() => {
console.log('Data added succesfully');
})
.catch((error) => {
console.log(error);
});
})
.catch((error) => {
console.log(error);
});
};
//Getting data from firestore, and added new properties
const getFromFirebase = async () => {
await newsOnFirebase();
var data;
await newsFromApiRef
.doc('testDocUID')
.get()
.then((querySnapshot) => {
const newData = [];
querySnapshot.data().articles.forEach((list) => {
if (list?.comments) {
newData.push({ ...list });
} else {
newData.push({
...list,
comments: [],
});
}
});
data = {
articles: [...newData],
status: querySnapshot.data().status,
totalResults: querySnapshot.data().totalResults,
};
})
.catch((error) => {
console.log(error);
});
return data;
};
If you want to get a document's data from Firestore and add new properties to this document, you need to first query for the document (with the get() method), as you do, and then write back to Firestore, either with the update() or set() methods (which you don't do).
Since you are using async/await, you can do along the following lines:
const snap = await newsFromApiRef.doc('testDocUID').get();
const docData = snap.data();
const newData = [];
docData.articles.forEach((list) => {
if (list?.comments) {
newData.push({ ...list });
} else {
newData.push({
...list,
comments: [],
});
}
});
const data = {
articles: [...newData],
status: docData.status,
totalResults: docData.totalResults,
};
await newsFromApiRef.doc('testDocUID').update(data);
Note that since you read one document, you are actually getting a DocumentSnapshot, and not a QuerySnapshot, hence the renaming in my anwser.

Node JS error when returning data from mongodb

I have this function
const run = async () => {
await LOLUserData.LOLUserData(3)
const LOLUserDataResult = await LOLUserData.LOLUserData()
console.log(LOLUserDataResult)
await app.listen(PORT, () => {
console.log(`Arena Gaming Server is listening on port ${PORT}!`)
})
}
which sends data to this function on startup
//=============================================================================
// [Mongoose] Get Active Sessions users data [userId, IGN, LOLSummonerId, LOLRegion] {Step (2)} {League of Legends Tracking}
//=============================================================================
const User = require('../../models/user')
const getLOLUserData = (userId) => {
// Get User data if (valid userId & IGN exsists)
User.findOne({'userId': userId, $and: [ { IGN: { $ne: '' , $exists: true} } ]})
.then(user => {
return ( [
user.userId,
user.IGN,
user.LOLRegion,
user.LOLSummonerId
])
} )
.catch(err => {
console.log(err)
})
};
exports.LOLUserData = getLOLUserData
The const LOLUserDataResult = await LOLUserData.LOLUserData()
console.log(LOLUserDataResult)
Should return the array from the previous function but instead i get an error
TypeError: Cannot read property 'userId' of null
What am I doing wrong here?
It looks like User.findOne() is not finding a record that matches your query. The query successfully executes, but finds no results. The promise resolves to null, indicating no record was found. Your then() callback runs, and tries to access user.userId, which is null.userId, which throws the exception.
In your then() callback, you should probably have something like this, to protect against getting no results.
.then(user => {
if (user) {
return [
user.userId,
user.IGN,
user.LOLRegion,
user.LOLSummonerId
]
} else {
return [] // or whatever makes sense.
}
} )
The solution was adding a callback argument and returning it
const getLOLUserData = (userId, callBack) => {
// Get User data if (valid userId & IGN exsists)
User.findOne({'userId': userId, $and: [ { IGN: { $ne: '' , $exists: true} } ]})
.then(user => {
let result = [
user.userId,
//user.IGN,
user.LOLRegion,
user.LOLSummonerId
]
return callBack(result)
})
.catch(err => {
console.log(err)
})
};
And in app.js I can use it like this
await LOLUserData.LOLUserData(3, async (result) => {
console.info(result)
})

Promise executes then function before previous then execution is completed

I'm try to chain a couple of then functions which execute sequentially, but the last .then() is being executed before the previous is done executing and as a result it sends an empty payload. Following is the snippet:
router.get("/selectedHotels", function(req, res) {
let payload = [];
return collectionRef
.where("isOwner", "==", true)
.get() //fetches owners
.then(snapshot => {
snapshot.forEach(user => {
console.log("User", user);
collectionRef
.doc(user.id)
.collection("venues")
.get() // fetches hotels from owners
.then(snapshot => {
snapshot.forEach(doc => {
if (
doc.data().location.long == req.query.long &&
doc.data().location.lat == req.query.lat
) {
console.log(doc.id, "=>", doc.data());
payload.push({
id: doc.id,
data: doc.data()
});
}
});
})
.catch(err => {
console.log("No hotels of this user", err);
});
});
})
.then(() => {
console.log("Payload", payload);
response(res, 200, "Okay", payload, "Selected hotels");
})
.catch(err => {
console.log("Error getting documents", err);
response(res, 404, "Data not found", null, "No data available");
});
});
Any suggestions? Thanks
Your main mistake is that you have a non-promise returning function, forEach, in the middle of your nested promise chain.
router.get('/selectedHotels',function(req,res){
let payload = [];
return collectionRef.where(...).get()
.then((snapshot)=>{
snapshot.forEach(user => {
// ^^^^^^^^^^^^^^^^^ this means the outer promise doesn't wait for this iteration to finish
// ...
The easiest fix is to map your array of promises, pass them into Promise.all and return them:
router.get('/selectedHotels',function(req,res){
let payload = [];
return collectionRef.where(...).get()
.then((snapshot)=> {
return Promise.all(snapshot.map(
// ...
return collectionRef.doc(user.id).collection('venues').get()
.then(...)
))
That being said, nesting promises like this is an anti-pattern. A promise chain allows us to propagate values through the then callbacks so there's no need to nest them.
Instead, you should chain them vertically.
Here's an example of how you can do that:
router.get("/selectedHotels", function(req, res) {
return collectionRef
.where("isOwner", "==", true)
.get() //fetches owners
// portion of the chain that fetches hotels from owners
// and propagates it further
.then(snapshot =>
Promise.all(
snapshot.map(user =>
collectionRef
.doc(user.id)
.collection("venues")
.get()
)
)
)
// this portion of the chain has the hotels
// it filters them by the req query params
// then propagates the payload array
// (no need for global array)
.then(snapshot =>
snapshot
.filter(
doc =>
doc.data().location.long == req.query.long &&
doc.data().location.lat == req.query.lat
)
.map(doc => ({ id: doc.id, data: doc.data() }))
)
// this part of the chain has the same payload as you intended
.then(payload => {
console.log("Payload", payload);
response(res, 200, "Okay", payload, "Selected hotels");
})
.catch(err => {
console.log("Error getting documents", err);
response(res, 404, "Data not found", null, "No data available");
});
});
Your using firestore so you need to give all documents to map and you also need to return some values to next then. I hope this will help you to solve your problem.
router.get('/selectedVenues',function(req,res){
return collectionRef.where('isOwner', '==', true).get()
.then(snapshot => {
let venues = [];
snapshot.docs.map(user => {
venues.push(collectionRef.doc(user.id).collection('venues').get());
});
return Promise.all(venues);
}).then(snapshots => {
let payload = [];
snapshots.forEach(venues => {
venues.docs
.filter(doc =>
doc.data().longitude == req.query.lng &&
doc.data().latitude == req.query.lat
)
.map(doc =>
payload.push({
id: doc.id,
data: doc.data()
})
)
});
return payload ;
}).then(payload => {
console.log('Payload', payload);
response(res, 200, "Okay", payload, "Selected hotels");
}).catch(err => {
console.log('Error getting documents', err);
response(res, 404, 'Data not found', null, 'No data available');
});
});
You're not returning a promise from within your first then, so there's no way for the code to know that it should wait for an asynchronous result.
router.get('/selectedHotels',function(req,res){
let payload = [];
return collectionRef.where('isOwner', '==', true).get() //fetches owners
.then((snapshot)=>{
var userVenuesPromises = [];
snapshot.forEach(user => {
userVenuesPromises.push(collectionRef.doc(user.id).collection('venues').get());
})
return Promise.all(userVenuesPromises);
})
.then((snapshots) => {
snapshots.forEach((snapshot) => {
snapshot.forEach((doc)=> {
if (doc.data().location.long == req.query.long && doc.data().location.lat == req.query.lat){
console.log(doc.id, '=>', doc.data());
payload.push({
id: doc.id,
data: doc.data()
});
}
});
});
return payload;
})
.then((payload) => {
...
In addition to using Promise.all() to ensure all nested loads are done before continuing to the next step, this also removes the nested promise, instead unpacking the values from the snapshots in a an additional step.
When chaining .then with asynchronous work, you need to return the promise you want to resolve before the next .then is executed. Something like this :
return Promise.all(snapshot.map(user => {
console.log("User", user);
return collectionRef.doc(user.id).collection('venues').get() // fetches hotels from owners
.then(snapshot => {
snapshot.forEach((doc)=> {
if (doc.data().location.long == req.query.long && doc.data().location.lat == req.query.lat){
console.log(doc.id, '=>', doc.data());
payload.push({
id: doc.id,
data: doc.data()
});
}
});
}).catch((err)=>{
console.log('No hotels of this user', err);
});
});
)
You can see it in action in this sample snippet :
function asyncStuff() {
return new Promise(resolve => {
setTimeout(() => {
console.log('async')
resolve();
}, 100)
});
}
function doStuff() {
console.log('started');
asyncStuff()
.then(() => {
return Promise.all([0,1,2].map(() => asyncStuff()));
})
.then(() => {
console.log('second then');
})
.then(() => console.log('finished'));
}
doStuff();
And see that without the return it gives your initial behaviour :
function asyncStuff() {
return new Promise(resolve => {
setTimeout(() => {
console.log('async')
resolve();
}, 100)
});
}
function doStuff() {
console.log('started');
asyncStuff()
.then(() => {
Promise.all([0,1,2].map(() => asyncStuff()));
})
.then(() => {
console.log('second then');
})
.then(() => console.log('finished'));
}
doStuff();

Error: Query.get failed: First argument must be an object

Using Firestor on React Native.
I am getting this error:
Error: Query.get failed: First argument must be an object
When I try to fetch User data from other collection(Item), but that would not work.
export const itemsFetch = () => {
return (dispatch) => {
firebase.firestore().collection('items').get()
.then((snapshot) => {
const items = snapshot.docs.map(doc => doc.data());
return items
})
.then((items) => {
const userPromises = items.map((item, itemId) => {
const userId = item.userId;
console.log('userId: ', userId);
return firebase.firestore().collection('users').get(userId)
.then((snapshot) => {
console.log('snapshot:', snapshot);
const user = snapshot.docs.data();
return user;
console.log('user:', user);
})
.then(user => ({...item, user: user, itemId}));
});
dispatch({ type: 'ui/loading/hide' });
return Promise.all(userPromises);
})
};
};
I could get data snapshot but I cannot realize still.
get() doesn't take an argument like this:
return firebase.firestore().collection('users').get(userId)
If you want to fetch a document by ID, you need to build a DocumentReference using doc(), and call get() on it:
return firebase.firestore().collection('users').doc(userId).get()
That will return a promise the yields a single DocumentSnapshot with the contents of that document.
See QuerySnapshot documentation. The .where is optional.
let query = firestore.collection('col').where('foo', '==', 'bar');
query.get().then(querySnapshot => {
let docs = querySnapshot.docs;
for (let doc of docs) {
console.log(`Document found at path: ${doc.ref.path}`);
}
});

Categories

Resources