How to get document after set method in firestore while updating - reactjs

I am making an update to my document. I use the set method because I want to overwrite my fields. Therefore, when i do my set, it works perfectly, modifying the object in the firestore database. But i am not able to return the document afterward. The error I get:
Cannot read property 'exists' of undefined
at Firebase.js:127
Here is my code:
const updateItemFromCollection = async (collectionName, uid, data) => {
return database
.collection(collectionName)
.doc(uid)
.set(data, { merge: true })
.then(doc => {
if (!doc.exists) {
console.log("No such document!"); //Error
} else {
return doc.data();
}
})
.catch(error => {
console.log(error);
});
};

This is because the set() method returns a Promise<void> and not a Promise<DocumentSnapshot<T>>.
You will need to read the document again, as follows:
const updateItemFromCollection = async (collectionName, uid, data) => {
const docRef = database.collection(collectionName).doc(uid);
return docRef.set(data, { merge: true })
.then(() => {
return docRef.get();
.then(doc => {
if (!doc.exists) {
console.log("No such document!"); //Error
} else {
return doc.data();
}
})
.catch(error => {
console.log(error);
});
};
This is the way Firestore works, and AFAIK, it is based on the fact that you already know the value of the document fields, since you passed a corresponding object to set(). One could remark however that this is not true for the values calculated in the back-end based on sentinel values, see FieldValue.

Related

Delete firebase document using field value

I am trying to delete a firebase document but the problem is I want to delete specific documents using fields.
as seen above I have user_uid_1 and user_uid_2 in many documents. and I want to match them like every document with (401 and 337) should be deleted when I click delete.
export const deleteChat = (chatId) => {
return async (dispatch) => {
const db = firestore();
db.collection("conversations")
.doc(chatId)
.delete()
.then(() => {
dispatch({
type: userConstants.GET_REALTIME_MESSAGES,
});
})
.catch((error) => {
console.log(error);
});
};
};
You could query using the where method and loop the delete() method for each document found. See sample code below:
const coversations = db.collection('conversations')
.where('user_id_1', '==', '401')
.where('user_id_2', '==', '337');
coversations.get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
doc.ref.delete();
});
});
If (401 and 337) can be in both user_id_1 and user_id_2, you can do a simple logic to check if there's an occurrence on the field. See sample code below:
const coversations = db.collection('conversations');
coversations.get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
const n = ['401', '307'];
if (n.includes(doc.data().user_uid_1) && n.includes(doc.data().user_uid_2)) {
doc.ref.delete();
}
});
});

How to get a single document from firestore?

According to the documentation from firebase you can get a document very simply by using get()
But for some reason in my code it always displays that there's no such document, even though it does exist, this is what I'm doing:
useEffect(() => {
console.log(user, "This is the user UID:"+user.uid)
const userDoc = db.collection('usuarios').doc(user.uid);
const doc = userDoc.get();
if (!doc.exists) {
console.log('No such document!');
}
else {
userDoc
.onSnapshot(snapshot => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setUserData(tempData);
})
}
}, [user]);
This is what the console.log() shows:
This is how it looks in firebase:
const doc = userDoc.get();
if (!doc.exists) {
.get returns a promise, so you're checking the .exists property on a promise, which is undefined. You will need to wait for that promise to resolve, either with .then:
userDoc.get().then(doc => {
if (!doc.exists) {
// etc
}
});
Or by putting your code in an async function and awaiting the promise:
const doc = await userDoc.get();
if (!doc.exists) {
// etc
}
If you're using the firebase 8 web version, the userDoc.get() returns a promise, not the document:
userDoc.get().then((doc) => {
if (!doc.exists) {
console.log('No such document!');
} else {
const tempData = [];
const data = doc.data();
tempData.push(data);
setUserData(tempData)
console.log('it worked')
}
}).catch((error) => {
console.log("Error getting document:", error);
});
You can get more info about promises in https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises.
In your code you are using the get method to fetch user data and get doesn't provide a snapshot. also, you missed that get() will return a promise so you have to handle using async-await or .then etc.
useEffect(() => {
console.log(user, "This is the user UID:"+user.uid);
getUser(user.uid).then(userData => {
setUserData(userData);
});
}, [user]);
const getUser = async (id) => {
try {
const user = await db.collection('usuarios').doc(id).get();
const userData = user.data();
return userData;
} catch (err){
console.log('Error during get user, No such document!');
return false;
}

How to insert array from another query as parameter in DB call FIREBASE

I'm trying to build compound query in Expo react native - firestore.
I have 2 collections in firebase. First "node" is userID and second are IDs of places that had been discovered by this user. Then, I need to take this array of place IDs and pass it as parameter in 2nd query where I got name of each place stored in collection named "databaseOfPlaces". (I want to make scrollable view with names, so maybe I should add listener later on?)
My solution is not working very well. Can you help me? Is this the right way, or is there another way how to save DB call?
Thank you very much.
This is my code:
async componentDidMount() {
db.collection("placesExploredByUsers") // default
.doc("mUJYkbcbK6OPrlNuEPzK") // default
.collection(auth.currentUser.uid)
.get()
.then((snapshot) => {
if (snapshot.empty) {
alert("No matching documents.");
return;
}
const users = [];
snapshot.forEach((doc) => {
const data = doc.data();
users.push(data);
});
this.setState({ users: users });
})
.catch((error) => alert(error));
db.collection("databaseOfPlaces")
.where('placeID','in',this.state.users)
.get()
.then((snapshot) => {
if (snapshot.empty) {
alert("No matching documents.");
return;
}
const places = [];
snapshot.forEach((doc) => {
const data = doc.data();
places.push(data);
});
this.setState({ places: places });
})
.catch((error) => alert(error));
}
Data is loaded from Firestore (and most modern cloud APIs) asynchronously. By the time your second query now runs, the results for the first query are not available yet.
Because of this, any code that needs the results from the first query, will need to be inside the then() callback of that query.
So:
async componentDidMount() {
db.collection("placesExploredByUsers") // default
.doc("mUJYkbcbK6OPrlNuEPzK") // default
.collection(auth.currentUser.uid)
.get()
.then((snapshot) => {
if (snapshot.empty) {
alert("No matching documents.");
return;
}
const users = [];
snapshot.forEach((doc) => {
const data = doc.data();
users.push(data);
});
this.setState({ users: users });
db.collection("databaseOfPlaces")
.where('placeID','in', users)
.get()
.then((snapshot) => {
if (snapshot.empty) {
alert("No matching documents.");
return;
}
const places = [];
snapshot.forEach((doc) => {
const data = doc.data();
places.push(data);
});
this.setState({ places: places });
})
})
.catch((error) => alert(error));
}

How to make asynchronous calls inside of a firestore .forEach loop?

Im using the firebase admin SDK server-side, and in one of my routes, im getting a collection, and then looping over each document in said collection...during each iteration, i use a property value to do a second get(). in the then() of this secondary get(), i again use data from the initial get to make final, tertiary get().
unfortunately the asynchronous nature of these nested calls seems to be creating undesirable outcomes.
heres the route function:
router.get('/list', authorization, (req, res) => {
console.log('/reports/list entered...')
admin
.firestore()
.collection('user-reports')
.get()
.then(querySnapshot => {
const reports = []
querySnapshot.forEach(snapshotDocument => {
const closed = snapshotDocument.get('closed')
console.log(`closed status: ${closed}`)
if (closed === false) {
const data = snapshotDocument.data()
console.log(`condition passed, data: ${JSON.stringify(data)}`)
// get # of reports made by sender
admin
.firestore()
.collection('users')
.doc(data.reportee)
.get()
.then(doc => {
data['reportee'] = {
reportActivity: doc.get('reportActivity')
}
console.log(`first then => data; ${JSON.stringify(data)}`)
// get report history of reportee
admin
.firestore()
.collection('users')
.doc(data.reporter)
.get()
.then(doc => {
data['reporter'] = {
reportActivity: doc.get('reportActivity')
}
console.log(`second then, ${JSON.stringify(data)}`)
reports.push(data)
})
.catch(err => {
return res.json({ error: true, message: err })
})
})
.catch(err => {
return res.json({ error: true, message: err })
})
}
})
console.log(`pre-response: ${JSON.stringify(reports)}`)
return res.json({ reports })
})
.catch(err => res.json({ error: true, message: err }))
})
what im logging is the "first condition passed", "pre-response: []", and "first then => data". by the time i ultimately return "reports" its empty. is there a more effective way to run firestore methods inside of foreach loops?
The solution almost always is to use Promise.all() to wait for a bunch of promises to resolve:
rturn Promise.all(querySnapshot.map(snapshotDocument => {
const closed = snapshotDocument.get('closed')
console.log(`closed status: ${closed}`)
if (closed === false) {
const data = snapshotDocument.data()
console.log(`condition passed, data: ${JSON.stringify(data)}`)
// get # of reports made by sender
return admin
.firestore()
.collection('users')
.doc(data.reportee)
.get()
.then(doc => {
data['reportee'] = {
reportActivity: doc.get('reportActivity')
}
console.log(`first then => data; ${JSON.stringify(data)}`)
// get report history of reportee
return admin
.firestore()
.collection('users')
.doc(data.reporter)
.get()
.then(doc => {
data['reporter'] = {
reportActivity: doc.get('reportActivity')
}
console.log(`second then, ${JSON.stringify(data)}`)
return data;
})
.catch(err => {
return res.json({ error: true, message: err })
})
})
.catch(err => {
return res.json({ error: true, message: err })
})
}
})
.then((reports) => {
console.log(`pre-response: ${JSON.stringify(reports)}`)
return res.json({ reports })
})
There might be some syntax errors, but the basic changes:
Turn the documents into a list of promises/results with querySnapshot.map(snapshotDocument => {.
Use Promise.all to create a promise that resolves when all these results are done.
Returns results from as deep down as needed all the way up to the main code.
Note that nesting of promises the way you do here is unusual and an anti-pattern, so in recommend also looking into learning to chain promises (so that you end up with a single catch - instead of having them all over the place.

Updating Data in Firebase using React

I am updating an object in firebase using React js.
I'm using this boilerplate as reference.
updateBookList: (id, data) => {
return firebaseDb.ref('NewBooks').child(id).update(data).then(() => {
return {};
}).catch(error => {
return {
errorCode: error.code,
errorMessage: error.message
}
});
},
The following updates the Books fine.
What I want to do is return the result instead of returning a blank {}. How can I return the result of what I updated?
This is how I fetch books:
fetchBooks: () => {
return new Promise((resolve, reject) => {
const bookSub = firebaseDb.ref('NewBooks').on("value", books => {
resolve(books.val());
}, error => {
reject(error);
})
})
},
If you want to return the value, you need to retrieve it. You can do that using once and the value event, which returns a promise that resolves to a Firebase snapshot:
updateBookList: (id, data) => {
let ref = firebaseDb.ref('NewBooks');
return ref
.child(id)
.update(data)
.then(() => ref.once('value'))
.then(snapshot => snapshot.val())
.catch(error => ({
errorCode: error.code,
errorMessage: error.message
}));
}
Also, you could simplify your fetchBooks by using once there, too:
fetchBooks: () => {
return firebaseDb.ref('NewBooks')
.once("value")
.then(snapshot => snapshot.val());
}
once returns a promise, so you don't have to create your own and you won't have a dangling event listener. The call to on in your implementation of fetchBooks will see a listener added with each call and multiple calls to resolve will be attempted if the database changes.

Resources