Firestore going up the hierarchy from subcollections [duplicate] - javascript

I have the following structure in my Firestore DB: several UserLists collections, which hold the Users. Each user may have a Notes subcollection.
I'm doing a subcollection group query that works well, returning notes from across the Notes subcollection.
My question is, from the Note document retrieved from the Notes subcollection, can I get the parent document ID? That would be User 1 in the below example. Using JavaScript.
Collection: UserList 1
Doc: User 1
Subcollection: Notes
Doc: Note 1
Collection: UserList 2
Doc: User 1
Subcollection: Notes
Doc: Note 1

You could use one of the following approaches:
const query = ......;
query
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.ref.path);
console.log(doc.ref.parent.parent.id);
});
})
On each QueryDocumentSnapshot, you can use the ref property, which returns a DocumentReference. Then, on this DocumentReference you use the path property, which will return the full path, e.g. UserList1/User1/Notes/Doc1.
Or you use the parent property of the DocumentReference, which returns a CollectionReference, then you use again the parent property (of the CollectionReference this time) to get the parent DocumentReference and then the id property of this parent DocumentReference.

Related

Firestore - Update an array of type DocumentReference

I am using Firestore as a quasi-relational database for a side project. In the Console GUI, I can create an array of DocumentReferences, and then use the values in that array to load child components when needed. I would like to do the same in my code but am unable to.
I have tried:
constructor(private store: AngularFirestore) {}
this.store
.collection('owners')
.doc(this.owner.id)
.update({
trucks: [
...this.owner.trucks, // firestore array of references
this.store.doc(`/truck/${documentReference.id}`),
],
});
But that stores the value as a string in Firestore.
Is there a way to store the value as an instance of DocumentReference?
You can use arrayUnion() if you want to push a new element to an array in Firestore document instead of fetching existing values first as shown below:
import { arrayUnion } from "firebase/firestore";
// ...
this.store
.collection('owners')
.doc(this.owner.id)
.update({
trucks: arrayUnion(this.store.doc(`/truck/${documentReference.id}`),
});
The value should be a DocumentReference if you pass the result of doc() directly in either of the code snippets. Can you confirm it by hovering on the field? It should show the type.

Firestore - Store a property id is reference type by firestore Batch transaction

I trying to store data content reference type by batch transaction, then I got an exception:
Function WriteBatch.set() called with invalid data. Unsupported field value: a custom object (found in document orders/OC9dZErupEhPsamp8QEd)
Is there a way we can use batch transaction to store reference type?
this is my code:
batch.update(orderRef, {
userId: firestore.doc(userId),
});
Normaly update() use to update existing firestore data. Review firestore docs for the same. In that given example they are updating population by increments of value or with new population number but before passing it in each update function values are stored in one cost value if it is not static value. as Asked by #dharmaraj please edit your questions by posting with full code you can also read given firestore documentation for your own studies.
import firebase from "firebase/app";
const app = firebase.initializeApp({});
const firestore = app.firestore();
const batch = firestore.batch();
const newUserId = firestore.doc(userId);
batch.update(orderRef, {
userId: newUserId,
});
Log newUserId value and see what are you getting into it.
You can't store the reference object that doc() returns, it's an object that may have circular references and functions in it. doc() is not the id of the document. If you want to get the id (which is a string), then:
const newUserId = firestore.doc(userId).ref.id;
batch.update(orderRef, {
userId: newUserId,
});
I don't know why batch validate input should be a pure object. I tried to push reference type id inside nested object then it work well, yeah I know it already is a trick, but it work.
change:
batch.update(docRef, {
user: firestore.collection('users').doc(userId)
})
to:
batch.update(docRef, {
user: {
id: firestore.collection('users').doc(userId)
}
})

How to get access from one collection to another collection in firebase

how to get access from one collection to another collection in firebase in JS v9
Firebase's JS API v9 brought different changes.
One of the biggest changes is the fact that the DocumentReference don't allow the access to subcollections anymore. Or at least, not directly from the DocumentReference itself, how we used to to with v8.
In v8, for example, we could do something like this:
//say we have a document reference
const myDocument = db.collection("posts").doc(MY_DOC_ID);
//we can access the subcollection from the document reference and,
//for example, do something with all the documents in the subcollection
myDocument.collection("comments").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// DO SOMETHING
});
});
With v9, we have a different approach. Let's say we get our document:
const myDocument = doc(db, "posts", MY_DOC_ID);
As you can note, the way we write the code is different. In v8 we used to write it in a procedural way. With v9, everything switched to a more functional way, where we can use functions such as doc(), collection() and so on.
So, in order to do the same thing we did with the above example and do something with every doc in the subcollection, the code for v9 API should look like this:
const subcollectionSnapshot = await getDocs(collection(db, "posts", MY_DOC_ID, "comments"));
subcollectionSnapshot.forEach((doc) => {
// DO SOMETHING
});
Note that we can pass additional parameters to functions such as collection() and doc(). The first one will always be the reference to the database, the second one will be the root collection and from there onward, every other parameter will be added to the path. In my example, where I wrote
collection(db, "posts", MY_DOC_ID, "comments")
it means
go in the "posts" collection
pick the document with id equals to MY_DOC_ID
go in the "comments" subcollection of that document

How to check that a document has been created in Firestore with RxFire

In my React app, I want to check whether a background firebase function has created the document in Firestore before updating my UI. So I use the docData function from RxFire to subscribe to changes in the specific, "about-to-be-created-from-a-background-function" doc:
const davidDocRef = db.doc('users/david');
// meantime, a firebase function creates "users/david" doc in the background
// I am observing for changes to the firestore doc in my React Component:
useEffect(() => {
docData(davidDocRef,'uid').subscribe(userData => setUserId(userData.uid));
...
// I then check that orgId is not null by assuming that I will get back a doc.id after it has been created it in Firestore
The problem is that userData.uid always returns "david" regardless of the doc being created in Firestore. What am I doing wrong? It seems like it's returning the uid from the path I've set in the reference instead of an actual document path that has been (or should have been) created in firestore. When I change the reference to look for "users/foo" then userData.uid still returns "foo" instead of undefined or throughing an error.
In this case I can instead look for userData.name. This actually works as I get "undefined" if the document has not been stored in Firestore or "David" if it has, but I would expect the example above to work in a similar manner.
The value of the "uid" field is not going to change as it is the reference to the document.
To confirm that the document was created, you should listen for changes in other fields, such as the name, as you already mentioned you do in your question.
Additionally, the same creation/write operations will invoke intermediately the updates.

How to get the last document from a VueFire query

Getting frustrated to solve this since I am no JS expert. 😢
I am using Firestore as a database and VuexFire to bind the data to VueX state, like so.
getLeads: firestoreAction(async ({
bindFirestoreRef
}) => {
// return the promise returned by `bindFirestoreRef`
return bindFirestoreRef('leads', db.collection('leads').orderBy('updated.date', 'desc').limit(30))
}),
It gets the first 30 results and then i want to implement an infinite scroll feature to run a function every time the scroll reaches the bottom and fetch more data and bind to the same state. In Firestore pagination require passing a query cursor of the last fetched document as a reference
Below from firebase document, with vanilla JS
var first = db.collection("cities")
.orderBy("population")
.limit(25);
return first.get().then(function (documentSnapshots) {
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
var next = db.collection("cities")
.orderBy("population")
.startAfter(lastVisible)
.limit(25);
});
since I use VuexFire to bind the data to state, I dont see an option to get the snapshot of the last document fetched by VuexFire (lastVisible from the above code), in order to pass it to the next query.
Any help will be highly appreciated. 🙏🏽
Lets say I have a collection of Customer records and i am displaying the first 5 ordered by last updated.
The query is
getLeads: firestoreAction(({ commit, bindFirestoreRef
}) => {
bindFirestoreRef('Leads', db.collection('leads')
.orderBy('updated.date', 'desc').limit(5)).then(documents => {
commit('POPULATE_TESTLEADS', documents);
commit('LAST_DOC', documents[documents.length - 1]);
});
}),
I am saving both the results and the lastdoc in the state, looping and showing the names, like so:
Nakheel
Emaar Group
Yapi Kredi Inc
Cairo Amman Bank
Arab Jordan Investment Bank LLC
I then call again with the last doc as query cursor and expect the next 5 docs to return from firebase, like so
moreLeadLeads: firestoreAction(({ state, bindFirestoreRef
}) => {
bindFirestoreRef('testLeads', db.collection('leads')
.orderBy('updated.date', 'desc')
.startAfter(state.lastDoc).limit(5))
}),
But I get the same 5 results as above from firestore. What am I doing wrong? :(
Internally VueFire and VuexFire use a serializer function that maps each Document returned by RTDB or Firestore into the data objects that are bound to the final component or Vuex store state.
The default serializer is implemented by the function createSnapshot that is part of the vuefire-core library:
/**
* #param {firebase.firestore.DocumentSnapshot} doc
* #return {DocumentData}
*/
export function createSnapshot (doc) {
// defaults everything to false, so no need to set
return Object.defineProperty(doc.data(), 'id', {
value: doc.id
})
}
As you can see it returns only doc.data() (with id added) and discards the doc object. However when implementing Firestore pagination via query.startAfter(doc) we need the original doc object. The good news is that VueFire and VuexFire APIs allow us to replace the serializer with our own that can preserve the doc object like so:
const serialize = (doc: firestore.DocumentSnapshot) => {
const data = doc.data();
Object.defineProperty(data, 'id', { value: doc.id });
Object.defineProperty(data, '_doc', { value: doc });
return data;
}
We can configure our new VuexFire serializer either globally via plugin options or per binding via binding options.
// Globally defined
Vue.use(firestorePlugin, { serialize });
// OR per binding
bindFirebaseRef('todos', db.ref('todos'), { serialize } )
For VuexFire, we can now get access to the first document as state.todos[0]._doc or last document state.todos[state.todos.length-1]._doc and use them to implement pagination queries for collections or "get next" & "get previous" queries that bind single documents (essential when your base query has multi-field sorting).
NOTE: Because _doc and id are non-enumerable properties, they won't appear on component or store objects in Vue DevTools.
From the VueFire documentation on binding data and using it, the $bind method (which I assume your bindFirestoreRef wraps) returns a promise with the result (as well as binding it to this). From that documentation:
this.$bind('documents', documents.where('creator', '==', this.id)).then(documents => {
// documents will point to the same property declared in data:
// this.documents === documents
})
So you should be able to do the same, and then get the document from the array with something like:
bindFirestoreRef('leads', db.collection('leads').orderBy('updated.date', 'desc').limit(30)).then(documents => {
this.lastDoc = documents[documents.length - 1];
})

Categories

Resources