Why I can't set state to Firebase's doc data? - javascript

I fetch data from my firestore database like thus:
const [songs, setSongs] = useState();
async function getSongs() {
const songs = collection(db, 'songs');
const songSnapshot = await getDocs(songs);
const songList = songSnapshot.docs.map(doc => doc.data());
setSongs(songList);
console.log(songList);
console.log(songs);
};
If I run this, there are two objects in the console, the first one is songList, and the second one is songs:
Why is there a difference between the two? What do I do to turn songs back into an array?

"songs" is a CollectionReference and does not contain the data. "songList" is an array which contains data from the documents returned by getDocs(). You should set songList in your state instead of songs
From the documentation,
A CollectionReference object can be used for adding documents, getting document references, and querying for documents (using query()).

Related

How to get the data of a user who created a document

I have two collections, one for registering properties and one for registered users.
The collection for registering properties is called listings and the collection for registered users is called users.
I access the documents inside the listings collection using the parameter listingId, which is the document ID.
I get the data from the document via the parameter I passed in:
const fetchListing = async () => {
const docRef = doc(db, 'listings', listingId)
// response
const docSnap = await getDoc(docRef)
if (docSnap.exists) {
listing = docSnap.data()
}
}
I need to get the fields (name,email, twitter, etc..) of the user who created the document.
How do I do this?
Each document has the ID of the user who creates it.
You have to do as follows:
Use the Object returned by the data() method, to get the user ID value (As explained in the doc, "this method retrieves all fields in the document as an Object").
Get the document corresponding to this user ID (exactly as you do to get the listing doc) and then, get the data of this document (again with the data() method).
const fetchListing = async () => {
const docRef = doc(db, 'listings', listingId);
const docSnap = await getDoc(docRef);
if (docSnap.exists) {
const userId = docSnap.data().user;
const userDocRef = doc(db, 'users', userId);
const userDocSnap = await userDocRef(docRef);
if (userDocSnap.exists) {
const userName = docSnap.data().name;
const userEmail = docSnap.data().email;
// ...
}
}
};
Note that a common approach for your use case in the NoSQL world is to denormalize your data in such a way that you get all the necessary data in your front-end in a minimum number of queries. Here is a "famous" post about NoSQL data-modeling approaches.
More concretely it means that in each Listing doc you would duplicate the data of its author/creator.
One side effect of this approach is that you need to keep the values in sync (i.e. the one in the User document and the ones in the Listing documents). This synchronization could be done with a Cloud Function triggered if the User doc is modified.

startAfter function always display the same data

I'm beginner to firestore.
My query always give the same data independently of the value i pass into startAfter() function.
here's code:
const fetchMore = async () => {
const q = query(
collection(db, 'chat-messages'),
orderBy('createdAt', 'asc'),
startAfter(23),
limit(5),
);
const querySnapshot = await getDocs(q)
querySnapshot.forEach(doc => {
console.log(doc.data())
})
}
Thanks for reading and helping!
Have a nice day
here's the console.log
I'm not sure what type the data in your startAt field is, but I guess it's a date or a Firestore Timestamp.
Pagination in Firestore is not based on numeric offsets, as you may be used to from other databases, but instead is based on so-called cursor objects: knowing the values and/or ID of the document you want to start after.
So you'll need to pass in the timestamp that you want to start after, not the number of documents you want to skip. So for example, to get all documents with a createdAt after now, you could do:
startAfter(Timestamp.now())
For more on this way of pagination, see the documentation on paginating data with query cursors.

How to use Promise.all with multiple Firestore queries

I know there are similar questions to this on stack overflow but thus far none have been able to help me get my code working.
I have a function that takes an id, and makes a call to firebase firestore to get all the documents in a "feedItems" collection. Each document contains two fields, a timestamp and a post ID. The function returns an array with each post object. This part of the code (getFeedItems below) works as expected.
The problem occurs in the next step. Once I have the array of post ID's, I then loop over the array and make a firestore query for each one, to get the actual post information. I know these queries are asynchronous, so I use Promise.all to wait for each promise to resolve before using the final array of post information.
However, I continue to receive "undefined" as a result of these looped queries. Why?
const useUpdateFeed = (uid) => {
const [feed, setFeed] = useState([]);
useEffect(() => {
// getFeedItems returns an array of postIDs, and works as expected
async function getFeedItems(uid) {
const docRef = firestore
.collection("feeds")
.doc(uid)
.collection("feedItems");
const doc = await docRef.get();
const feedItems = [];
doc.forEach((item) => {
feedItems.push({
...item.data(),
id: item.id,
});
});
return feedItems;
}
// getPosts is meant to take the array of post IDs, and return an array of the post objects
async function getPosts(items) {
console.log(items)
const promises = [];
items.forEach((item) => {
const promise = firestore.collection("posts").doc(item.id).get();
promises.push(promise);
});
const posts = [];
await Promise.all(promises).then((results) => {
results.forEach((result) => {
const post = result.data();
console.log(post); // this continues to log as "undefined". Why?
posts.push(post);
});
});
return posts;
}
(async () => {
if (uid) {
const feedItems = await getFeedItems(uid);
const posts = await getPosts(feedItems);
setFeed(posts);
}
})();
}, []);
return feed; // The final result is an array with a single "undefined" element
};
There are few things I have already verified on my own:
My firestore queries work as expected when done one at a time (so there are not any bugs with the query structures themselves).
This is a custom hook for React. I don't think my use of useState/useEffect is having any issue here, and I have tested the implementation of this hook with mock data.
EDIT: A console.log() of items was requested and has been added to the code snippet. I can confirm that the firestore documents that I am trying to access do exist, and have been successfully retrieved when called in individual queries (not in a loop).
Also, for simplicity the collection on Firestore currently only includes one post (with an ID of "ANkRFz2L7WQzA3ehcpDz", which can be seen in the console log output below.
EDIT TWO: To make the output clearer I have pasted it as an image below.
Turns out, this was human error. Looking at the console log output I realised there is a space in front of the document ID. Removing that on the backend made my code work.

How to get single data from collection

I'm using vue3 and firestore
Referring to the firestore official document, there was a way to get documents through collection. But this is the way to get all the collection data.
const citiesRef = db.collection('cities');
const snapshot = await citiesRef.get();
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
});
I want to get a single document through collection.
How to get a single documemt is
const cityRef = db.collection('cities').doc('SF');
const doc = await cityRef.get();
This is a collection followed by a doc().
I can't insert a value in the doc because documemt is an auto-generated ID.
So I don't know what to do.
You have not mentioned the which document you are specifically looking for. In case you don't know the ID of the Firestore Document, you can try running a simple query using .where() like this:
const db = firebase.firestore()
db.collection("cities").where("cityName", "==", 'London').get().then(querySnapshot => {
const matchedDoc = querySnapshot.doc[0]
})
The example above finds the document where the field cityName is equal to London (both case sensitive). QuerySnapshot contains all the documents which matched your condition specified in the where() method. But in case you know there is only one matching document, as in my case I only have one doc with cityName as London, you can directly access the doc data this way: querySnashots.docs[0].data(). querySnapshot.docs is an array. [0] is the first document in it and .data() parses data from the doc. Additionally you can use querySnapshot.size to get count of documents returned from the query.

Get the child object of a limit(1) query on firebase database

I have this data
I do this query
firebase.database().ref(${path}/buy)
.orderByChild('price').limitToFirst(1)
.once('value')
.then(snapshot => console.log(snapshot.val()))
And I get this result
Then the question is
Is there an easy way to access the price attribute of the one object whose key I don't know?
e.g. snapshot.first().price or snapshot.only().price
Simply put, I want to avoid this
var result = snapshot.val()
var key = Object.keys(result)[0]
var price = result[key].price
When you execute a query against the Firebase Database, there will potentially be multiple results. So the snapshot contains a list of those results. Even if there is only a single result, the snapshot will contain a list of one result.
In your callback you need to handle this list by using snapshot.forEach():
firebase.database().ref(${path}/buy)
.orderByChild('price').limitToFirst(1)
.once('value')
.then(snapshot => {
snapshot.forEach(function(child) {
console.log(child.val());
console.log(child.val().price);
})
})

Categories

Resources