react-native-firebase query with 'array-contains' in useEffect keeps refreshing component - javascript

I'm just trying to make a request to fetch only the threads that contain the current user's id.
If I remove my 'where' query, I can fetch all threads.
There is my code :
useEffect(() => {
const unsubscribe = firestore()
.collection('THREADS')
// query is empty
.where('usersIds', 'array-contains', ['60ddd70c7a3a1e8e62d14dac'])
.orderBy('latestMessage.createdAt', 'desc')
.onSnapshot(querySnapshot => {
const threadsQueried = querySnapshot
? querySnapshot.docs.map(documentSnapshot => {
return {
...documentSnapshot.data(),
};
})
: null;
setThreads(threadsQueried);
if (loading) {
setLoading(false);
}
});
return () => unsubscribe();
});
I already tried without putting my id into an array, but the component keeps refreshing, like that:
.where('usersIds', 'array-contains', '60ddd70c7a3a1e8e62d14dac')
My firebase datas:
I already check here https://stackoverflow.com/a/59053018/9300663
and here https://stackoverflow.com/a/59215461/9300663
Edit: So it is working when id is without brackets ('60ddd70c7a3a1e8e62d14dac') into the query
But my component keeps refreshing.
If I add an empty array or an array with dependencies to my useEffect, the query does not works anymore.
Edit 2: Query is working but get called two times and the second time get back with 'null', which is emptying my state.

So I found the solution when I tried another way to get my firebase query by using get() instead of onSnapshot():
firestore()
.collection('THREADS')
.where('usersIds', 'array-contains', user.id)
.orderBy('latestMessage.createdAt', 'desc')
.get()
.then(querySnapshot => {
const threadsQueried = querySnapshot.docs.map(documentSnapshot => {
return {
...documentSnapshot.data(),
};
});
setThreads(threadsQueried);
The problem with 'get()' was that the query only worked once and didn't update if new threads were created.
But it allowed me to have a firebase error asking me to create the indexes: 'usersIds' and 'latestMessage.createdAt'. After creating them, I was able to reuse my old code and everything's working correctly now.
useEffect(() => {
const unsubscribe = firestore()
.collection('THREADS')
.where('usersIds', 'array-contains', user.id)
.orderBy('latestMessage.createdAt', 'desc')
.onSnapshot(querySnapshot => {
const threadsQueried = querySnapshot.docs.map(documentSnapshot => {
return {
...documentSnapshot.data(),
};
});
setThreads(threadsQueried);
});
return () => unsubscribe();
}, []);

Related

How to fetch all the documents with unique id from firestore database using React?

[Firestore SS][1]
[1]: https://i.stack.imgur.com/EI1Dm.png
I want to fetch each document as displayed in SS it's stored as Pets + unique_userId.
I am unable to fetch all data together. Just able to fetch one data of a particular user using the code below.
const [info,setInfo]=useState([]);
useEffect(() => {
db.collection("pets ESYXOPqlJpZ48np8LfNivnh9pvc2").onSnapshot((snapshot) =>
setInfo(snapshot.docs.map((doc) => doc.data()))
);
},[]);
Here ESYXOPqlJpZ48np8LfNivnh9pvc2 this is the userID of each unique user
Please help me out to fetch all the Pets data instead of hardcoding and fetching one particular data.
Try the following code,
const [docs, setDocs] = useState([]);
useEffect(() => {
const querySnapshot = await getDocs(collection(db,"pets ESYXOPqlJpZ48np8LfNivnh9pvc2"));
const document =[];
querySnapshot.forEach((doc) => {
document.push({
...doc.data(),
id: doc.id
});
});
setdocs(document);
}, []);
I'm guessing the appended id is a reference to the owner's id? In this case, would it be an option to fetch the owner list and use everyone's id to build a list of collection ids and then get all of their data?
If not, I only see to options:
Rethink your database structure - maybe use a unified pets collection and have a reference with/to that id in the pet documents.
Create a cloud function in which use #google-cloud/firestore to get the list of collections. There are tons of resources out there to help you get started with firebase cloud functions. Their documentation is pretty good also, and probably the most up-to-date
const functions = require('firebase-functions')
const { Firestore } = require('#google-cloud/firestore');
module.exports = functions
.region('europe-west3') // use the region you want here
.https.onRequest(async (request, response) => {
try {
const firestore = new Firestore();
const collections = (await firestore.listCollections()).map(collection => collection.id)
response.json({ data: collections })
} catch (error) {
response.status(500).send(error.message)
}
})
You'll get and endpoint which you can use to fetch the collection ids (e.g.: https://your-project-name.cloudfunctions.net/collections)
const [pets, setPets] = useState([]);
const [collectionIds, setCollectionIds] = useState([])
useEffect(() => {
fetch('https://your-project-name.cloudfunctions.net/collections')
.then(response => response.json())
.then(({ data }) => setCollectionIds(data))
}, [])
useEffect(() => {
collectionIds.forEach((collectionId) => {
// There are better ways to do this,
// I'm just using your approach so you can focus on the rest of the code
db.collection(collectionId).onSnapshot((snapshot) => {
setPets((currentPets) => [...currentPets, ...snapshot.docs.map((doc) => doc.data())])
})
})
}, [collectionIds])
Please note that these are very high-level implementations, there's no error handling, no teardowns or anything, so keep that in mind. Hope it helps, good luck!

React Native Firebase adding and getting data

wondering if anyone can assist me in this matter. I'm following the documentation for https://rnfirebase.io/firestore/usage. it does not work for my use case for some reason.
I just need to set the data, which it works and then read it back so i can push it onto my state and i'll render it.
I just can't read the data back properly. This addItemFunction is trigger when when user click on a button to add.
const addItemFunction = async (numb,exercise) =>{
firestore().collection(userEmail).get().then((snap) =>{
if(!snap.empty){
var finalID = uuid.v4();
firestore().collection(userEmail).doc(final).update({
[finalID]:{
exe:[exercise],
num:[numb],
}
}).then(() =>{
//RETURN SNAPSHOT NOT WORKING
console.log('user_added');
firestore().collection(userEmail).doc(final).onSnapshot(documentSnapshot =>{
console.log("here" + documentSnapshot.data());
});
}
Thanks for your time.
If you are using react with hooks I would suggest you put the onSnapshot listener in a useEffect hook:
useEffect(() => {
const unsubscribe = firestore
.collection(collectionName)
.doc(docId)
.onSnapshot(
(documentSnapshot) => {
const document = documentSnapshot.data();
console.log(document)
},
(error: Error) => {
throw error;
}
);
return () => unsubscribe();
}, [ docId, collectionName]);
this approach will separate concerns and the snapshots will run every time there is a change on the document, then where I put the console.log you could set the document to state.
Another approach will be to use get() instead of onSnapshot like:
const addItemFunction = async (numb,exercise) =>{
firestore().collection(userEmail).get().then((snap) =>{
if(!snap.empty){
var finalID = uuid.v4();
firestore().collection(userEmail).doc(final).update({
[finalID]:{
exe:[exercise],
num:[numb],
}
}).then(() =>{
console.log('user_added');
firestore().collection(userEmail).doc(final).get().then(() => {
console.log("here" + documentSnapshot.data());
})
}
}
}
this approach will not subscribe to changes and it will return the new updated document every time you call the addItemFunction

Why my React app runs firebase query in useEffect when I alter the database?

My code is supposed to load data on the first render so I fetched the documents from firebase in useEffect and stored them in the state "data". It works fine, all the documents are displayed on the page as I wanted. Now I wanted to delete individual documents on clicking a button under each of them. I did so with the help of a deleteDoc function which deletes the document from the database (and a file from storage if it exists). The deletion works fine but after the delete is successful, my code reloads the data from the database again for some reason causing the app to rerender the page again. I console logged at 2 different places in useEffect and found that useEffect doesn't execute the whole code inside it but only the part where it fetches the data from firebase. I just want it to perform that operation in the background and not interfere with the data state again. So how can I stop this re fetching every time I delete a document?
const [data, setData] = useState([]);
useEffect(() => {
if (user) {
user.getIdTokenResult().then((idTokenResult) => {
setAdmin(idTokenResult.claims.admin);
});
db.collection("files")
.orderBy("timeStamp", "desc")
.limit(5)
.onSnapshot((snapshot) => {
console.log(data, "In query"); // This executes after the delete operation
setData(
snapshot.docs.map((doc) => {
return [doc.data(), { doc_id: doc.ref.id }];
})
);
});
}
console.log(data, "Outside query"); // This doesn't execute after the delete operation
}, []);
Delete function:
const deleteDoc = async (e, item) => {
e.preventDefault();
// Deleting file from storage if it exists
if (item[0].file_name) {
const deleteTask = storage
.ref("users/" + user.uid)
.child(item[0].file_name);
deleteTask
.delete()
.then(() => {
console.log("File deleted Successfully");
})
.catch((err) => console.log(err));
}
// Deleting document from firestore collection.
db.collection("files")
.doc(item[1].doc_id)
.delete()
.then(() => {
setData(
data.filter((doc) => {
return doc[1].doc_id !== item[1].doc_id;
})
);
console.log("Document successfully deleted!", data);
})
.catch((error) => {
console.error("Error removing document: ", error);
});
};
By calling onSnapshot to get the data from Firestore, you're attaching a permanent listener that listens for realtime updates. So it is indeed expected that your callback gets called again (and thus the state updated again) when the database changes.
If you only want to get the data once, use get() instead of onSnapshot():
db.collection("files")
.orderBy("timeStamp", "desc")
.limit(5)
.get().then((snapshot) => {
...
According this document we can listen to changes in collection using onSnapshot So when you delete document in db, onSnapshot will listen to new change and will execute logic inside that function.
Solution to this is to Detach a listener
const unsubscribe = db.collection("files")
.orderBy("timeStamp", "desc")
.limit(5)
.onSnapshot(() => {
// Respond to data
// ...
});
// Later ...
// Stop listening to changes
unsubscribe();
Second solution is to use another state let's say fetchData and once you receive data on load set it to false like below:-
const [data, setData] = useState([]);
const [fetchData, setFetchData] = useState(true);
useEffect(() => {
if (user) {
user.getIdTokenResult().then((idTokenResult) => {
setAdmin(idTokenResult.claims.admin);
});
db.collection("files")
.orderBy("timeStamp", "desc")
.limit(5)
.onSnapshot((snapshot) => {
if(fetchData) {
setData(
snapshot.docs.map((doc) => {
return [doc.data(), { doc_id: doc.ref.id }];
})
);
setFetchData(false);
}
});
}
}, []);

Cloud Firestore working fine with .get() but not with .onSnapshot() using next.js

I'm having some trouble that I'm not understanding very well playing with next.js and Firebase's Cloud Firestore, but basically this works:
export async function fetchBroadcasts() {
const db = await loadDB();
const firestore = db.firestore();
const settings = { timestampsInSnapshots: true };
firestore.settings(settings);
return await firestore.collection('broadcasts').doc('message').get().then(doc => ({ broadcast: doc.data() }));
}
and this doesn't:
export async function fetchBroadcasts() {
const db = await loadDB();
const firestore = db.firestore();
const settings = { timestampsInSnapshots: true };
firestore.settings(settings);
return await firestore.collection('broadcasts').doc('message').onSnapshot(doc => ({ broadcast: doc.data() }));
}
I can't figure out why the second option doesn't work since I'm basically following the documentation.
On my index.js page I have this:
static async getInitialProps() {
return fetchBroadcasts();
}
onSnapshot doesn't return a promise, so you can't await it. As you can see from the linked API docs, it returns a function that you call when you want to stop the listener that you just added.
You use onSnapshot when you want to set up a persistent listener on a document that constantly receives changes to that document. You use get when you want a single snapshot of that document.
Firestore's onSnapshot() always return documents array regardless of query you use:
firestore.collection('broadcasts').doc('message')
.onSnapshot(docs => {
return docs[0].data();
});
While in case of get you can get single document as well on the base of query:
firestore.collection('broadcasts').doc('message')
.get( doc => ({ broadcast: doc.data() }) );

Error with Firestore update in a Cloud Function

I try to put a listener on Firebase that will replicate a value in the matching element in Firestore.
exports.synchronizeDelegates = functions.database.ref(`delegates/{userId}/activities`).onUpdate((event) => {
const userKey = event.data.ref.parent.key
console.log("User Key:" + userKey)
return admin.database().ref(`delegates/${userKey}/email`).once('value', snapshot => {
let email = snapshot.val()
console.log("Exported Email:" + email)
const userRef = admin.firestore().collection('users')
const firestoreRef = userRef.where('email', "==", email)
firestoreRef.onSnapshot().update({ activities: event.data.toJSON() })
}).then(email => {
console.log("Firebase Data successfully updated")
}).catch(err => console.log(err))
}
)
This function is able to retrieve and locate the elemnt needed to target the right document in firestore, but the .update()function still error firestoreRef.update is not a function
I try several ways to query but I still have this error.
How to properly query then update a document in this scenario?
The onSnapshot() method of Query introduces a persistent listener that gets triggered every time there's a new QuerySnapshot available. It keeps doing this until the listener is unsubscribed. This behavior is definitely not what you want. Also, there's no update() method on QuerySnapshot that your code is trying to call.
Instead, it looks like you want to use get() to fetch a list of documents that match your query, then update them all:
exports.synchronizeDelegates = functions.database.ref(`delegates/{userId}/activities`).onUpdate((event) => {
const userId = event.params.userId
console.log("User Key:" + userKey)
return admin.database().ref(`delegates/${userId}/email`).once('value', snapshot => {
let email = snapshot.val()
console.log("Exported Email:" + email)
const usersRef = admin.firestore().collection('users')
const query = usersRef.where('email', "==", email)
const promises = []
query.get().then(snapshots => {
snapshots.forEach(snapshot => {
promises.push(snapshot.ref.update(event.data.val()))
})
return Promise.all(promises)
})
}).then(email => {
console.log("Firebase Data successfully updated")
}).catch(err => console.log(err))
}
Note that I rewrote some other things in your function that were not optimal.
In general, it's a good idea to stay familiar with the Cloud Firestore API docs to know what you can do.

Categories