How to enable persistence on reactfire? - javascript

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

Related

React FIrebase Failed-precondition

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...

FIrebase firestore onSnapshot not firing on change

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,

I want to access my state variable from one component to other

I have a react query which writes the state variable- follower, and I want to access this variable in other component to find its .length can someone tell me how do I do it
const ModalFollower = ({profile}) => {
const [follower,setFollower] = useState([])
const {
data: followerName,
isLoading: followerLoading,
isFetching: followerFetching
} = useQuery(["invitations", profile?.id], () => {
getFollowers(profile?.id).then((response) => {
if (response) {
setFollower(response);
}
});
});
return(
{
!followerLoading && (
follower.map((e) => {
return(<>
<p>{e.requested_profile.Userlink}</p>
</>}
)
}
)
I want to access the length of follower in some other component
There is no need to copy data from react-query to local state, because react-query is a full-blown state manager for server state. As long as you use the same query key, you will get data from its cache. This is best abstracted away in custom hooks.
Please be aware that with the default values, you will get a "background refetch" if a new component mount, so you will see two network requests if you use it twice. That might look confusing at first, but it is intended, as it is not react-query's primary goal to reduce network requests, but to keep your data on the screen as up-to-date as possible. So when a new component mounts that uses a query, you'll get the stale data from the cache immediately, and then a background refetch will be done. This procedure is called stale-while-revalidate.
The best way to customize this behaviour is to set the staleTime property to tell react-query how long your resource is "valid". For that time, you will only get data from the cache if available. I've written about this topic in my blog here: React Query as a State Manager.
React Query also provides selectors, so if your second component is only interested in the length, this is what my code would look like:
const useInvitations = (profile, select) =>
useQuery(
["invitations", profile?.id],
() => getFollowers(profile?.id),
{
enabled: !!profile?.id
select
}
)
Note that I also added the enabled property because apparently, profile can be undefined and you likely wouldn't want to start fetching without that id.
Now we can call this in our main component:
const ModalFollower = ({profile}) => {
const { data } = useInvitations(profile)
}
and data will contain the result once the promise resolves.
In another component where we only want the length, we can do:
const { data } = useInvitations(profile, invitations => invitations.length)
and data will be of type number and you will only be subscribed to length changes. This works similar to redux selectors.

invalid hook call on data retrieved by axios

I'm trying to retrieve the JSON data from here and then update the state in a functional component. Even though the code seems fine I'm getting an error saying its an invalid hook call.
On the react documentation it said that I might have 2 different react apps in the same folder however I checked it with the command they gave and there was only 1. However I am running this from a django server and there is a different react app in a different django app (so in a completely different folder).
const App = () => {
const [posts, setPosts] = useState([]);
useEffect(() => {
let url = "https://jsonplaceholder.typicode.com/posts";
axios.get(url).then(res => {
console.log(res.data);
// The code crashes here saying that it is an invalid hook call
useState(res.data);
}).catch(err => console.log(err));
}, []);
return (
<div>
This is just a place holder.
</div>
);
}
I have a feeling this might have something to do with the other react application in the django project but if anyone can see something that I can't I would appreciate the help.
Edit
I realised that I was trying to call useState() in the useEffect hook when I should have been using the setPosts function that was already defined in the function.
// The code crashes here saying that it is an invalid hook call
It crashes there because you are not using the state hook correctly. You should call setPosts instead of useState. See docs.

Get array of objects from real time data snapshot - Cloud Firestore

I'm trying to fetch real time data from Cloud Firestore using the below code.
export const getRealTimeData = () =>
db
.collection('posts')
.onSnapshot(
(querySnapshot) => {
const posts: any = [];
querySnapshot.forEach((doc) =>
posts.push(Object.assign({
id: doc.id
}, doc.data()))
);
},
);
};
And, I want to use the resultant array to display the data on UI. When I'm doing this, the resultant array is a function but not the actual array of data.
const posts = getRealTimeData();
Here's what I get when I log posts
function () {
i.kT(), o.al(s);
}
Could anyone please point where I went wrong?
Realtime listeners added with onSnapshot() are not compatible with returning values from function calls. That's because they continue to generate new results over time, and would never really "return" anything once. You should abandon the idea of making a synhronous getter type function in this case - they just don't work for what you're trying to do.
Ideally, you would use an architecture like Redux to manage the updates as they become available. Your realtime listener would dispatch query updates to a store, and your component would subscribe to that store that to receive those updates.
If you don't want to use Redux (which is too bad - you really should for this sort of thing), then you should wrap your query inside a useEffect hook, then have your listener set a state hook variable so your component can receive the updates.

Categories

Resources