I am trying to create a list of all documents contained in my main root collection.
I have data like this:
collection -> doc -> collection -> doc
Apologies if posts - posts is confusing!
And this is the data at root level:
I was thinking something like this would work:
const [posts, setPosts] = useState();
useEffect(() => {
const db = firebase.firestore();
return db.collection('posts').onSnapshot((snapshot) => {
const postData = [];
snapshot.forEach((doc) => postData.push({ ...doc.data(), id: doc.id }));
setPosts(postData);
});
}, []);
console.log(posts);
but this just logs an empty array when it should be an array of 2 objects.
Can anyone shine some light on where i'm going wrong? And if i will be able to access the nested data from doing this?
Update after your comment and the screenshot addition
We can see that the documents in the parent posts collection are displayed with an italic font in the Firebase console: this is because these documents are only present (in the console) as "container" of one or more sub-collection but they are not "genuine" documents.
As matter of fact, if you create a document directly under a col1 collection with the full path doc1/subCol1/subDoc1, no intermediate documents will be created (i.e. no doc1 document).
The Firebase console shows this kind of "container" (or "placeholder") in italics in order to "materialize" the hierarchy and allow you to navigate to the subDoc1 document but doc1 document doesn't exist in the Firestore database.
Let's take an example: Imagine a doc1 document under the col1 collection
col1/doc1/
and another one subDoc1 under the subCol1 (sub-)collection
col1/doc1/subCol1/subDoc1
Actually, from a technical perspective, they are not at all relating to each other. They just share a part of their path but nothing else. One side effect of this is that if you delete a document, its sub-collection(s) still exist.
So, it is normal that you get an empty array when doing db.collection('posts').onSnapshot((snapshot) => {...}), because you are listening to un-existing documents.
What you could do is to listen for the documents in one of the posts subcollection, as follows:
db.collection('posts').doc('frlGy...').collection('posts').onSnapshot((snapshot) => {...});
Another possibility would be to use a Collection Group query, to get all the elements in ALL the posts subcollections. But in this case you cannot have the parent and subcollections sharing the same name (i.e. posts).
Update following your second comment
Will I need to do it differently if I, for example, want to be able to
log all posts from all Users, as the Users documents technically wont
exist?
It all depends if you have some user documents or not, which is not clear.
If you do, for each of those user documents, create a posts subcollection, and use db.collection('Users').doc(user.uid).collection('posts').onSnapshot((snapshot) => {...});
You can also use a Collection Group query to query across the different posts subcollections.
If you don't have user documents, you can have a global posts collection (root collection) and you save the user uid as a field of each post document. This way you can secure them (with Security rules) and also query all posts by user. You can also easily query posts across all users.
You need to put the console.log within the callback, as follows:
const [posts, setPosts] = useState();
useEffect(() => {
const db = firebase.firestore();
return db.collection('posts').onSnapshot((snapshot) => {
const postData = [];
snapshot.forEach((doc) => postData.push({ ...doc.data(), id: doc.id }));
console.log(postData); // <------
setPosts(postData);
});
}, []);
Also note that this will only return the frlGy... document, not the one in the subcollection: as said above, Firestore queries are shallow. If you want to query the subcollection, you need to execute an additional query.
const getMarkers= async ()=> {
await db.collection('movies').get()
.then(querySnapshot => {
querySnapshot.docs.forEach(doc => {
setlist(doc.data().title);
});
});
}
Its so simple just add async and await on a function and call the function in useeffect
Related
I have a snapshot listener attached to a subcollection (this subcollection logs all changes to parent documents). For each document in this subcollection, I want to retrieve the parent docuemnt. What should I replace XXXXXXXXXXX with in my code below?
const unsubToProfilesRecentlyUpdatedByUser = onSnapshot(qChangedByThisUser, (querySnapshot) => {
store.state.currentProfileList = [];
querySnapshot.forEach((doc) => {
const parentProfileRef = doc(db, "profiles", XXXXXXXXXXX);
//Get the parent Profile of this Activitylog
getDoc(parentProfileRef)
.then((profileSnap) => {
//store resulting profile in object
let profile = profileSnap.data()
profile.id = profileSnap.id
store.state.currentProfileList.push(profile)
})
});
//console.log("Current cities in CA: ", cities.join(", "));
});
I have tried many examples from other posts, and I manage to make it work when listening only to changes, in which case it is
const parentProfileRef = doc(db, "profiles", change.doc.ref.parent.parent.id);
But I can't make is work when listening to the whole state like above.
Thanks for any hint!
If doc is a DocumentSnapshot object, then you can get a DocumentReference of its parent like this:
const parentRef = doc.ref.parent.parent;
You can then pass that to getDoc(). There is no need to build another DocumentReference with doc().
This works for all DocumentSnapshot objects. It is not special to the method you used to get the DocumentReference. Be sure to use the linked API reference to discover how to use various objects you get back from the Firestore API.
I have this collection in my Firestore:
Collection: Team1
Document: TeamName
Subcollection: Notes
Subcollection: Users
Document: user1 => Field: name: "Anna", uid: "someID"
Document: user2 => Field: name: "John", uid: "someID"
Lets imagine a scenario in which there are many Team collections with the schema above. How to return the whole Team collection with JavaScript (so I can access for example Notes) based on the Name field in Document (in Users subcollection) .
I tried this:
var nameRef = db
.collection('Team1')
.doc('TeamName')
.collection('Users')
.where('name', '==', 'Anna')
const getData => ()=> {
nameRef.get().then((snapshot) => {
snapshot.docs.forEach((doc) => {
console.log(doc.data())
})
})
}
However the code above outputs only the document fields in the User Doc (name, uid).
Your query will only return documents from the Users collection. If you also want to show data from the user, you will need to load their document separately. For the TeamName user, that'd be:
db
.collection('Team1')
.doc('TeamName')
.get()
Or alternatively you can determine and get the parent document for the user with:
doc.ref.parent.parent.get()
If you want to search across all teams for users named Anna, you can use a collection group query. This is a special type of query that searches across all collections with a specific name.
var nameRef = db
.collectionGroup('Users')
.where('name', '==', 'Anna')
const getData => ()=> {
nameRef.get().then((snapshot) => {
snapshot.docs.forEach((doc) => {
console.log(doc.data())
})
})
}
Here again, this only loads users and not their teams yet. But you can use the same doc.ref.parent.parent.get() snippet as before to then also find and load the team document for each user.
In addition to the .data() method, the retrieved doc also has a .parent property which contains a CollectionReference - in this case, it'd reference your Users collection. That collection also has a .parent property that will point to your TeamName document. It's parent will point to your Team1 collection.
Using these parent properties of the documents and collections, you can work your way 'up' the tree to your Team collection upon which you can retrieve its documents.
i want to ask something, i need to fetch data from firebase, BUT only the list of the documents
Like This
i've already tried, and i'm stuck (at the moment), here's my code:
this.ref3 = firebase
.firestore()
.collection("pendatang")
.orderBy("timestamp", "desc")
.limit(10);
componentDidMount = async () => {
this.unsubscribe = this.ref3.onSnapshot(this.onCollectionUpdate2);
};
onCollectionUpdate2 = (querySnapshot) => {
const tanggal = [];
querySnapshot.forEach((doc) => {
tanggal.push({
tanggal: doc.id,
doc, // DocumentSnapshot
});
});
this.setState(
{
tanggal,
},
() => {
console.log(this.state.tanggal);
}
);
};
Actually i didn't get error, i got the array but it's like didn't get any, when i use map, it didn't appear anything inside the state, this is what i got after the fetch The Image
i tried map it:
{this.state.tanggal.map((item, key) => (
<Chip
avatar={<Avatar>M</Avatar>}
label={item.tanggal}
onClick={() => this.ChipsClicked(24)}
/>
))}
and what i got:
Nothing
I really need help guys :( and I'm actually very curious about firebase, I really appreciate everyone who wants to help me :)
As shown in your Firebase console screenshot, the document is displayed with an italic font in the Firebase console: this is because this document is only present (in the console) as "container" of one or more sub-collection but it is not a "genuine" document.
If you create a subDoc1 document directly under a col1 collection with the full path doc1/subCol1/subDoc1, no intermediate documents will be created (i.e. no doc1 document).
The Firebase console shows this kind of "container" (or "placeholder") in italic in order to "materialize" the hierarchy and allow you to navigate to the subDoc1 document but doc1 document doesn't exist in the Firestore database.
So if we have a doc1 document under the col1 collection
col1/doc1/
and another one subDoc1 under the subCol1 (sub-)collection
col1/doc1/subCol1/subDoc1
... actually, from a technical perspective, they are not at all relating to each other. They just share a part of their paths but nothing else.
You can very well create subDoc1 without creating doc1.
Another side effect of this is that if you delete a document, its sub-collection(s) still exist. Again, the subcollection docs are not really linked to the parent document.
So, in your case, if you need to have genuine docs in the pendatang collection, you need to create them at the same time you create the first subcollection doc.
Also, note that Cloud Firestore has shallow reads: querying for documents in a collection doesn't pull in data from subcollections.
I'm building a chat app. When a user makes an update on their local profile I'd like to use cloud functions to make that update across a collectionGroup.
I'm successfully listening to the update in cloud functions and retrieving a list of collectionGroups with the following:
const collectionGroupNameref = await db.collectionGroup('collectionGroupName').where('userId', '==', data.uid).get();
collectionGroupNameref.forEach(async (val: any) => {
const connectionsRef = await db.collection('collectionGroupName').doc(val.id).get();
});
But now I need to update a field within that collectionGroup and that's where I'm running into issues.
The collectionGroup is stored in 2 locations:
users{id}collectionGroupName{id}
groups{id}collectionGroupName{id}
Is it possible to update all of the documents in that collectionGroup
Firestore doesn't provide any methods to update an entire collection or collection group like "UPDATE WHERE" in SQL. What you are required to do instead is write each document individually. So, if you've already executed a query for documents in the collection group, can you simply iterate the documents in the result set and update each document as needed. You can use the ref property of DocumentSnapshot to easily update each document, no matter what collection contains it.
const querySnapshot = await db
.collectionGroup('collectionGroupName')
.where('userId', '==', 'data.uid')
.get();
querySnapshot.docs.forEach(snapshot => {
snapshot.ref.update(...)
})
What I need:
I want to save articles or notes in Firestore with their respective fields:
Title
Content (texts or paragraphs)
Creation date
Owners (to share that article with other
people and who can edit them like: https://firebase.google.com/docs/firestore/solutions/role-based-access)
But when I show the list of articles I don't need the "content" field (to save bandwidth). I've read that (maybe I'm wrong), it is not possible to make a query to get only specific fields from a document with Firestore.
If it were normal SQL to obtain specific columns from articles (without its content) It would be something like:
SELECT title, creation_date, ...
FROM table_name;
So I've opted to separate the content for two root-level collections (for flexibility and scalability)
My current structure:
Articles collection:
- `articles` [collection]
- `ARTICLE_ID` [document]
- `creatorId` [field]
- `title` [field]
- `date` [field]
- `owners` [obj field]
- {user1_id}: true
- {user2_id}: true
...
Contents collection:
- `contents` [collection]
- `{ARTICLE_ID}` [document]
- `content` [field]
To get articles list in realtime:
firebase.firestore().collection('articles')
.where(`owners.${user.uid}`, '==', true)
.onSnapshot(querySnapshot => {
const articles = []
querySnapshot.forEach((doc) => {
articles.push({
id: doc.id,
...doc.data()
})
})
// do something with articles array
})
To show in another view and get the entire article with its content:
const db = firebase.firestore()
const articleRef = db.collection('articles').doc(articleId)
const contentRef = db.collection('contents').doc(articleId) // same Id as article
articleRef.get().then(articleDoc => {
if (articleDoc.exists) {
contentRef.get().then(contentDoc => {
if (contentDoc.exists) {
const article = {
...articleDoc.data(),
...contentDoc.data()
}
// full article obj
}
})
}
})
My questions
Do you think it's better to do two queries (getArticle and getContent) at the same time and wait with Promise.all() instead of nesting the querys like I do?
Is there a better way to get the article and its content with one query or more efficiently? Some tips or ideas?
Thank you very much in advance!
According to the Firestore Query.select documentation you should be able to select the fields you want.
let collectionRef = firestore.collection('col');
let documentRef = collectionRef.doc('doc');
return documentRef.set({x:10, y:5}).then(() => {
return collectionRef.where('x', '>', 5).select('y').get();
}).then((res) => {
console.log(`y is ${res.docs[0].get('y')}.`);
});
Neither approach is pertinently better than the other. But there are a few key differences.
When you nest the reads, the second read only starts after the first read has completed. When you use Promise.all() both reads start at the same time, so can (partially) run in parallel.
On the other hand: when you use Promise.all() your completion handler (the code you run in then()) won't execute until both documents have loaded. If you nest the calls, you can update the UI after just the first document has loaded.
In the end, the differences are likely to be small. But since they may be significant to your use-case, measure the results and see what works best for you.
In order to output a single field from a Firestore document (version 9) - for example the 'title' in the articles collection you can use the following code snippet:
const q = query(collection(db, 'articles'))
let results = [];
await getDocs(q);
results = getLocation.docs.map((doc) => doc.data()['title']);
results.sort()
The results array will contain only the title field, sorted alphabetically
(Note you have to reference the Firestore db and import 'getDocs', 'query' and 'collection' modules from Firestore)
Firebase Hosting would be your best bet for static content such as articles. If you look at AMP-HTML for example, they strongly make the case for ultra-fast page loads and highlight benefits of edge caching. Firebase hosting is advertised to also support global edge caching.
Firestore and Firebase Realtime Database are database engines. These are not the proper tool for serving up articles.