I am following a simple tutorial on Firebase 9. Everything worked fine until I tried to use onSnapshot method to get the snapshot of changes in db every time the cahnge occurs.
But onSnapshot is not working. I mean it work only for the initial fetch of data, but does not execute the callback function when the change occurs.
I am following a tutorial so my code is identical to the code from the tutorial. My question is whether there is something I need to do in the console to rectify this, some kind of rules or permissions or something.
My code:
const colRef = collection(db, 'books');
const q = query(colRef, where('author', '==', 'patrick rothfuss'));
onSnapshot(q, (snapshot) => {
let books = []
snapshot.docs.forEach(doc => {
books.push({ ...doc.data(), id: doc.id })
})
console.log(books)
})
You can use snapshot.docChanges().forEach method to read and detect changes in data on real time.
I recommend you use this method to modify the array when a change occur.
You can view the code here: https://github.com/firebase/snippets-web/blob/1c4c6834f310bf53a98b3fa3c2e2191396cacd69/snippets/firestore-next/test-firestore/listen_diffs.js#L8-L23
For me, the issue was that I imported collection from the lite version:
import { collection } from "firebase/firestore/lite";
After I changed it
import { collection } from "firebase/firestore";
All is working well. Also, make sure other functions are imported from a normal version.
You can read more about the lite version here:
https://firebase.google.com/docs/firestore/solutions/firestore-lite
You can see it doesn't support snapshot listeners.
Cheers,
Related
Im using Firebase to store some data in my React app. Its working fine on my local emulator with the same settings, but when i publish the app, i get this error:
#firebase/firestore: Firestore (9.12.1): Uncaught Error in snapshot listener: {"code":"failed-precondition","name":"FirebaseError"}
Im using the where() clause, and i red i need to add some sort of index in my firebase rules.
useEffect(() => {
if (activeList === 'null') {
} else {
const userRef = collection(database, activeList.id);
const sort = query(userRef, where("checked", "==", showDoneTodos), orderBy('title', 'asc'))
const unsubsctibeAllTodos = onSnapshot(sort, (snapshot) => {
setAllTodos(snapshot.docs.map(doc => ({
id: doc.data().id,
title: doc.data().title,
desc: doc.data().desc,
checked: doc.data().checked
})
))
})
return () => {
unsubsctibeAllTodos()
}
}
}, [activeList, showDoneTodos])
Some info:
activeList is an object with and ID. The problem is that this is generated live so i cant preconfigure any collection ID before i publish.
showDoneTodos is a boolean.
Any guidance would be very welcome!
Thanks!
Firebase logs this error when the required index is missing. It only shows this error when you use the onSnapshot method. When you use the getDocs methode it will show the good old link to create the index in the Firebase console. A solution is to create a similar request with the getDoc methode, create the index and switch back to the onSnapshot methode.
It seems that Firebase does not provide a link to create the necessary index anymore. They only provide the error code:{"code":"failed-precondition","name":"FirebaseError"}
The solution is the same, go to Firebase -> Indexes and add it there...
I'm trying to console log a query of all the available documents within the Posts collection in firebase. Can someone explain why .get() is not a function when I'm literally using the same code from firebase docs?
const posts = collection(db, 'Posts');
async function getAllDocs() {
const snapshot = await posts.get();
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
});
}
You're mixing two versions of the Firebase API that are not compatible with each other.
Your first line uses the newer v9 modular API, while the like that is failing is trying to use the older v8 namespaced API. The two can't be mixed like that.
If you want to use the v9 modular API, the failing line should be:
const snapshot = getDocs(posts);
When doing this type of development I always keep the Firebase documentation handy, as it has side-by-side snippets of the two API versions. For example, this specific case is covered in the documentation on getting all documents in a collection.
I'd like to implement Firestore offline persistence on my PWA React app using the reactfire library.
const firestore = useFirestore().enablePersistence();
let documentReference = firestore
.collection("food")
.doc("milkshake");
const { data } = useFirestoreDocData(documentReference);
but running the code i get an error:
FirebaseError: Firestore has already been started and persistence can no longer be enabled. You can only enable persistence before calling any other methods on a Firestore object.
This component is wrapped inside a <Suspense> as mentioned in the documentation
That database read is the only one that i make in the entire app, how can i solve?
Edit.
Using the example that #Ajordat gave, I've imported the preloadFirestore function inside the App component I do get an error:
"Cannot read property 'name' of undefined".
Whereas adapting (because I cannot use hooks inside the fetch function)
the example from #DougStevenson: I've imported useFirestore function in the App component (in order to get the Firestore object) to enable persistence, and then importing it (useFirestore) into my component in order to retrieve the data, but now, I get the same error as before,
Firestore has already been started and persistence can no longer be enabled.
Edit 2:
I've tried to enablePersistence without errors, thank guys, this is my approach, let me know if it is the best:
const firestore = useFirestore();
React.useEffect(() => {
firestore.enablePersistence();
}, []);
And in my custom component:
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let document = useFirestoreDocDataOnce(docRef);
console.log(document)
But now I do have a problem, when I log the document, the data are not emitted instantly, yeah I know that it is an asynchronous operation, but the component is wrapped inside a <Suspense>, in this way:
<Suspense fallback={<div>Loading</div>}>
<FoodComponent foodName={"Milkshake"} />
</Suspense>
But I don't see the loading text before the component is actually rendered.
Does the suspense fragment show the fallback component only while is loading the function (useFirestore) and not the actual data?
Well, I've solved, have to destructure the data, doing like that:
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let { data: document } = useFirestoreDocData(docRef);
console.log(document)
On other JavaScript libraries for Firestore, enablePersistence() returns a promise. That means it will complete some time in the future, with no guarantees how long it will take. If you're executing the query immediately after you call enablePersistence(), without waiting for the returned promise to become fulfilled, then you will see this error message. That's because the query "beats" the persistence layer and effectively executes first.
You will have to figure out how to use that promise to wait until it's OK to make that query with persistence enabled. For example:
seFirestore().enablePersistence()
.then(() => {
let documentReference = firestore
.collection("food")
.doc("milkshake");
const { data } = useFirestoreDocData(documentReference);
})
.catch(error => {
console.error("enablePersistence failed", error);
})
Notice how the query will complete only after the persistence is fully enabled.
Thanks for the suggestion guys #DougStevenson and #Ajordat
In app component:
import { useFirestore } from "reactfire"
...
const firestore = useFirestore();
React.useEffect(() => {
firestore.enablePersistence();
}, []);
In your custom component, where you want to use Firestore:
import { useFirestore, useFirestoreDocData /* or what you want to use */ } from "reactfire"
let docRef = useFirestore()
.collection("food")
.doc("milkshake");
let { data: document } = useFirestoreDocData(docRef);
console.log(document);
I've a simple question but I can't find the answer.
I'm using Vuexfire to import data from Firebase in Vuex.
const state = {
ricettario: [] // data that contains all recipes (objects)
}
const actions = {
// Get data from Firebase
init: firestoreAction(({ bindFirestoreRef }) => {
bindFirestoreRef('ricettario', db.collection('ricettarioFirebase'))
}),
}
It works perfectly but I want to manipulate every document of the collection 'ricettarioFirebase'. With vue + firebase was easy, with .get() and .then()
I can't find a solution! I thought using GETTERS is the best way to do that but I'm new with Vuex and Vuexfire so I don't know how to do that.
In particular, I want to convert this (classic firebase command):
db.collection("ricettarioFirebase")
.orderBy("data")
.get()
.then(snapshot => {
snapshot.forEach(doc => {
let ricetta = doc.data();
ricetta.data = dayjs(ricetta.data)
.locale("it")
.format("D MMMM YYYY");
ricetta.diffClass = ricetta.diff.split(" ").join("_");
this.ricettario.push(ricetta);
});
});
In Vuexfire. So change every object in the "ricettario[ ]" will be "ricetta", and I want to edit the "ricetta.diffClass" and "ricetta.data"
As you will see in the Vuexfire documentation:
Vuexfire does not handle writing data back to Firebase because you can
directly use the Firebase JS SDK to precisely update whatever you
need.
The doc gives some example of use of the "standard" JS SDK, exactly like you did in your question ("classic firebase command"). So you'll have to go this way, wrapping the database writes in Vuex Actions.
I'm using Firestore in conjunction with realtime database in order to provide a user presence system for my application.
Update: article I followed
https://blog.campvanilla.com/firebase-firestore-guide-how-to-user-presence-online-offline-basics-66dc27f67802
In one of the methods I use this code here:
const usersRef = this.$fireStore.collection('users').doc(uid)
const whoIsOnlineRef = this.$fireDb.ref('.info/connected')
whoIsOnlineRef.on('value', (snapshot) => {
this.$fireDb.ref(`/status/${uid}`)
.onDisconnect()
.set('offline')
.then(() => {
usersRef.set({ online: true }, { merge: true })
this.$fireDb.ref(`/status/${uid}`).set('online')
})
})
The .set method, however, is giving me the error mentioned in the title and I can't quite understand why. I'm simply passing a javascript object to .set method and this should technically work.
Can you spot what is wrong with the code?
Looks like the reason why this wasn't working is that the code was running on the server in an SSR application.
I moved that very same logic to the browser and it started working nicely. I still don't see why this wouldn't work in the server as, at the end of the day I was still passing a simple js object to the .set() method.
Either way, there you have it