Firestore get() and onSnapshot functions not working in useEffect - reactjs

I am trying to get all the documents from a Firestore collection. I am following their docs to do so. However, nothing happens. I do not see anything console-logging or any errors whatsoever. Basically, nothing happens, as if the get() function is not working. I am clueless as to what is going on.
Here's the code with the get(). It is written in ReactJS with hooks:
import React, { useState, useEffect } from 'react';
import firebase from '../firebase.js';
import './Messages.css';
function Messages (props) {
const [messages, setMessages] = useState([]);
const [customers, setCustomers] = useState([]);
useEffect(() => {
const query = firebase
.firestore()
.collection("Chats")
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
if (doc.exists) {
console.log(doc)
} else {
console.log('nothing')
}
console.log(doc.id, " => ", doc.data());
});
}).catch((error) => {
console.log("Error getting documents: ", error);
});
}, []);
Similarly, the same thing was happening to the onSnapShot function that I tried to use as follows:
useEffect(() => {
const unsub = query.onSnapshot(async querySnapshot => {
if (querySnapshot.docChanges().length > 0) {
const data = querySnapshot.docs.map((doc) => {
return { body: doc.data(), id: doc.id }
}
);
const allData = await Promise.all(data)
setCustomers(allData);
} else { console.log("no data") }
}, (error) => {
console.log("Error listening to documents: ", error);
})
return () => {
unsub();
};
}, []);
The Customers state is empty and console-logging from anywhere within Querysnapshot function does not work. Strangely, however, similar set-ups/codes work in my other components. Adding dependencies or placing the code into another function (fetchData()) and calling it within useEffect did not work. I am also certain it is not an issue with Rules as I am accessing Firestore as an admin for which I have an appropriate Rule set up (match /{documents=**} { allow write, read: if request.auth.token.admin == true; }). I am clueless as to what is going on. Please help.
P.S. The following code does work within the useEffect in the same component, however, but since I can not figure out how to get rid of duplicates that I get from the query in the customers state (tried array.includes and indexof but failed) as I kept getting only one same object, I am ditching the following code in favor of one of the above.
const query = firebase.firestore()
.collectionGroup('Messages')
.orderBy("timestamp", "desc")
const unsub = query.onSnapshot((snapshot) => {
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
snapshot.docs.map((doc) => {
console.log("1")
if (customers.length ) {
console.log("1b")
customers.map((customer) => {
console.log("2")
if (customer.uid.indexOf(doc.data().uid) === -1) {
console.log("3")
setCustomers(prevFiles => ([...prevFiles, {
name: doc.data().name, uid: doc.data().uid
}]))
}
})
} else {
console.log("4")
setCustomers([{name: doc.data().name, uid: doc.data().uid}])
}
});
}, (error) => {
console.log('Error getting messages', error);
})

I figured it out. This might be very useful for somebody who is in a similar situation to know as this does not seem to be explained in the Firestore docs.
Anyways, the reason nothing was happening and console.logging was not working was because the query was empty. So, make sure to always check that your collection has documents which in turn have some information. The documents that I was trying to get contained a collection and named after user ids which I needed. However, since they did not really contain any information, they could not be retrieved. And, I did not know that. I simply needed the names of the documents, the uids.
One way you can check if your snapshot is empty is as follows:
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
In my code, it looks like this:
const query = firebase
.firestore()
.collection("Chats")
.get()
.then((querySnapshot) => {
if (querySnapshot.empty) {
console.log('No matching documents.');
return;
}
querySnapshot.forEach((doc) => {
if (doc.exists) {
console.log(doc)
setCustomers(prevFiles => ([...prevFiles, {
id: doc.data().id, data: doc.data()
}]))
} else {
console.log('nothing')
}
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
}).catch((error) => {
console.log("Error getting documents: ", error);
});
Basically, it will never get to the forEach part if it is empty.
Hope this sheds some light on some confusion that someone might have about the docs in collections. They must contain info to be retrievable. Happy coding.

Related

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));
}

Undefined data (sometimes data)

I'm trying to read an Array inside my FireStore document. I want to render the items inside this Array in a component through using .map().
Sometimes, I get a TypeError: Cannot read property 'map' of undefined error. What could be causing it and how can I ensure that it doesn't happen.
interface Product {
summary: string;
details: string;
product: string;
benefit: Array<string>;
}
function ProductInfo({ product }: { product: Product }) {
console.log("Product:",product.summary);
product.benefit.forEach((item) => { //triggers exception sometimes
console.log(item)
})
}
In a different component, this is how I populate the data and pass it to the component above:
function ProductDetails({ match }: RouteComponentProps<TParams>) {
const [product, setProduct]: any = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
const response = await db.collection("Products").doc(match.params.id).get();
console.log('response', response.data());
let data: any = { title: 'not found' };
if (response.exists) {
data = response.data();
}
setProduct(data);
} catch (err) {
console.error(err);
}
};
fetchData();
}, []);
return (
<div>
<ProductInfo product={product} />
</div>
)
}
I'm learning React with TypeScript so I'm still trying to get the hang of things. I'm confused as to why it works sometimes and does not at other instances. product.summary gets rendered all the time though. Thank you
EDIT
From debugging, I think I seem to have found the issue:
If I test with:
const [product, setProduct]: any = useState();
useEffect(() => {
const fetchData = async () => {
try {
const response = await db.collection("Products").doc("flex-crm").get();
console.log('response', response.data());
let data: any = { title: 'not found' };
if (response.exists) {
data = response.data();
}
setProduct(data);
} catch (err) {
console.error(err);
}
};
fetchData();
}, []);
console.log("Data: ", product)
I get 3 lines of output in the console (instead of 2). I get
Data: undefined
response {...}
Data: {...}
My prop is using the Data:undefined instance when the component is rendered. How can I update it to use the fetched data?
Try moving setProduct(data); inside the if condition. Maybe for some products response is not present and you are still setting data which will be undefined in that case.
if (response.exists) {
data = response.data();
setProduct(data);
}
Try to use product?.summary.map(...).
Probably this will help you.
~Also why did you use response.data() i could not get it, shouldn't it be response.data?

Running firebase request in a foreach but want to wait for the result to proceed (async/await not working)

I'm using a react useEffect hook and inside that hook I'm fetching data in a foreach from firebase nothing weird here and this seems to be working.
I want to wait for foreach and requests to finish so my array will be build and I can use it in my following code. But it seems my code is not waiting before continuing (the console.log inside the foreach is shown last in my console).
useEffect(() => {
const unsubscribe = firebase
.locations()
.once('value')
.then(async snapshot => {
if (snapshot.val() !== null) {
const locationObject = snapshot.val()
const userInformation = []
await Object.keys(locationObject).forEach(element => {
firebase.user(element).once('value', userSnapshot => {
if (userSnapshot.val() !== null) {
console.log('inside location')
userInformation.push({
userId: 1,
name: userSnapshot.val().username
})
}
})
})
console.log('usrInfo', userInformation)
}
})
})
What am I doing wrong here? Any help would be appreciated!
The Object.keys(locationObject).forEach code doesn't return a Promise, so calling await on it won't do anything.
If you want to wait for multiple promises to resolve, you'll typically want to use Promise.all. I'd also in general recommend using DataSnapshot.forEach() instead of Object.keys().forEach() as it maintains the order of the child nodes.
So combined that'd become something like:
const unsubscribe = firebase
.locations()
.once('value')
.then(async snapshot => {
let promises = [];
snapshot.forEach(element => {
promises.push(firebase.user(element.key).once('value'))
})
return Promise.all(promises);
}).then(snapshots => {
const userInformation = []
snapshots.forEach(userSnapshot => {
if (userSnapshot.exists()) {
userInformation.push({
userId: 1,
name: userSnapshot.val().username
})
}
})
console.log('userInfo', userInformation)
})

How do I return an Array from asyncData in nuxt from firestore for SSR?

I am attempting to get my app to render my data in an array from Firestore in SSR using asyncData. My function outside of asyncData is working fine but I can NOT get it to work inside.
I have tried so many ways of getting the array from asyncData to render. I trying to return the data onto 'addons' so it fills out the form the same way my method does.
Here is my working method
async getAddons() {
var addonsRef = db
.collection('addons')
.where('publish', '==', true)
.orderBy('timeUpdated', 'desc');
await addonsRef.get().then(snapshot => {
snapshot.forEach(doc => {
const addons = {
...doc.data(),
id: doc.id
};
return this.addons.push(addons);
});
});
},
And here is the code I am currently trying inside asyncData
async asyncData({ app, error }) {
const addonsRef = await db
.collection('addons')
.where('publish', '==', true)
.orderBy('timeUpdated', 'desc');
try {
await addonsRef.get().then(snapshot => {
snapshot.forEach(doc => {
const addons = {
...doc.data(),
id: doc.id
};
return app.addons.push(addons);
});
});
} catch (e) {
// TODO: error handling
console.error(e);
}
return app.addons.push(addons);
},
I expect the asyncData code to populate the array so it fills out my template the same as the getAddons function.
What simple thing am I missing?
you should return dictionary from asyncData. E.g.
{
something: app.addons.push(addons)
}
And then in template you access it via {{something. }}

Resources