How to check whether I'm getting data from cache or firestore (JavaScript SDK)? - javascript

I enabled the firestore cache by calling the enableIndexedDbPersistence method in my react app. here is my code:
import { initializeApp } from "firebase/app"
import { getAuth } from "firebase/auth"
import { enableIndexedDbPersistence, getFirestore } from "firebase/firestore"
const firebaseConfig = {
// config code
}
initializeApp(firebaseConfig)
const db = getFirestore()
enableIndexedDbPersistence(db) // enable cache
const auth = getAuth()
export { auth, db }
I would like to know how I can verify that I'm getting data from the cache, not from the server in the below code?
import { useEffect, useState } from "react"
import { collection, limit, onSnapshot, orderBy, query, where } from "firebase/firestore"
import { db } from "../firebase/config"
export const useCollection = (c) => {
const [documents, setDocuments] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let ref = collection(db, c)
const unsubscribe = onSnapshot(ref, (snapshot) => {
const results = []
console.log(snapshot.metadata.fromCache ? "local data" : "server data")
snapshot.docs.forEach(
(doc) => {
results.push({ ...doc.data(), id: doc.id })
},
(error) => {
console.log(error)
setError("could not fetch the data")
}
)
setDocuments(results)
setIsLoading(false)
setError(null)
})
return () => unsubscribe()
}, [])
return { documents, error, isLoading }
}
Is there anything apart from calling the enableIndexedDbPersistence method I need to do to configure the cache properly?
Will this method cache the subcollection documents as well?

Related

Firestore get count for a collection not working

I have this code:
import { getCountFromServer } from 'firebase/firestore'
useEffect(()=> {
const getCount = async () => {
const collection = userDB().collection('invoices')
const query = collection.where('status', '==', 'paid')
const snapshot = await getCountFromServer(query)
setCount(snapshot.data().count)
}
getCount().catch(err=> console.log(err))
}, [user, collectionRef, filterKey, filterValue])
Where userDB() is the same as db.collection('users').doc(userid')
Yet this throughs an error:
TypeError: Cannot read properties of undefined (reading '_t')
at gn (index.esm2017.js:4251:1)
at bu (index.esm2017.js:13685:1)
at Ca.run (index.esm2017.js:16797:1)
at index.esm2017.js:20050:1
I have followed the docs: https://firebase.google.com/docs/firestore/query-data/aggregation-queries#web-version-9_1
Currently using:
"firebase": "^9.15.0",
userDB is defined in a ts file like so:
export const userDB = () => {
let user = auth.currentUser
return db.collection('users').doc(user?.uid)
}
Here is the initlizer file:
import firebase from 'firebase/compat/app';
import 'firebase/compat/firestore';
import 'firebase/compat/auth';
const firebaseApp = firebase.initializeApp ({
...keys
});
export const Providers = {
google: new firebase.auth.GoogleAuthProvider(),
};
const db = firebaseApp.firestore()
const auth = firebase.auth();
const Fire = firebaseApp
export {db, Fire, auth}
You should not uses both compat and modular SDKs together. The compat version does not support count queries. I would recommend updating your code to use the new syntax:
import { initializeApp } from 'firebase/app';
import { getFirestore } 'firebase/firestore';
import { getAuth, GoogleAuthProvider } 'firebase/auth';
const firebaseApp = initializeApp({
...keys
});
export const Providers = {
google: new GoogleAuthProvider(),
};
const db = getFirestore()
const auth = getAuth();
export { db, auth }
import { db } from '...'
import { collection, query, where, getCountFromServer } from 'firebase/firestore'
useEffect(()=> {
const getCount = async () => {
const q = query(collection(db, 'invoices'), where('status', '==', 'paid'))
const snapshot = await getCountFromServer(q)
setCount(snapshot.data().count)
}
getCount().catch(err=> console.log(err))
}, [user, collectionRef, filterKey, filterValue])

Unable to process request due to missing initial state( Firebase)

A user of my website encountered an error message while attempting to sign up using GitHub on my React site from his chrome browser(mobile). I have integrated Firebase GitHub sign-in with the popup method.
Unable to process request due to missing initial state. This may happen if browser sessionStorage is inaccessible or accidentally cleared.
Code for signup:
import { useEffect, useState } from "react"
import { GithubAuthProvider, signInWithPopup } from "firebase/auth"
import { auth } from "../firebase/config"
import { createUserProfileDocument } from "../firebase/createUserProfileDocument"
import { useAuthContext } from "./useAuthContext"
export const useSignup = () => {
const [error, setError] = useState(false)
const [isPending, setIsPending] = useState(false)
const [isCancelled, setIsCancelled] = useState(false)
const provider = new GithubAuthProvider()
const { dispatch } = useAuthContext()
const signup = async () => {
setError(null)
setIsPending(true)
try {
const res = await signInWithPopup(auth, provider)
if (!res) {
throw new Error("Could not complete signup")
}
const user = res.user
await createUserProfileDocument(user)
dispatch({ type: "LOGIN", payload: user })
if (!isCancelled) {
setIsPending(false)
setError(null)
}
} catch (error) {
if (!isCancelled) {
setError(error.message)
setIsPending(false)
}
}
}
useEffect(() => {
return () => setIsCancelled(true)
}, [])
return { signup, error, isPending }
}
useAuthContext code:
import { useContext } from "react"
import { AuthContext } from "../context/AuthContext"
export const useAuthContext = () => {
const context = useContext(AuthContext)
if (!context) {
throw Error("useAuthContext must be used inside an AuthContextProvider")
}
return context
}
AuthContext code
import { createContext, useEffect, useReducer } from "react"
import { onAuthStateChanged } from "firebase/auth"
import { auth } from "../firebase/config"
import { authReducer } from "../reducers/authReducer"
export const AuthContext = createContext()
export const AuthContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(authReducer, {
user: null,
authIsReady: false,
})
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
dispatch({ type: "AUTH_IS_READY", payload: user })
})
return unsubscribe
}, [])
return (
<AuthContext.Provider value={{ ...state, dispatch }}>{children}</AuthContext.Provider>
)
}
firebaseConfig object:
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_API_KEY,
authDomain: process.env.NEXT_PUBLIC_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_APP_ID,
measurementId: process.env.NEXT_PUBLIC_MEASUREMENT_ID,
}
initializeApp(firebaseConfig)
const db = getFirestore()
const auth = getAuth()
export { auth, db, logEvent }

Firestore uid not available after page reload

I am exporting a function returning firestore data useing the uid as identifier. The uid however is not available after reloading the page, causing af can't read null value error. I tried researching and happened upon the shown onAuthStateChanged, but this is causing the error: TypeError: Cannot read properties of undefined (reading 'indexOf'). Would appreciate the help.
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
import {
getAuth,
onAuthStateChanged } from "firebase/auth";
import { useState, useEffect} from "react";
import {
getFirestore,
collection,
getDocs,
addDoc,
setDoc,
doc } from 'firebase/firestore';
const firebaseConfig = {
apiKey: "AIzaSyCJtckBTE3-ub4JP6NcEJX_PKao7r0YJRw",
authDomain: "dtustudenthub.firebaseapp.com",
projectId: "dtustudenthub",
storageBucket: "dtustudenthub.appspot.com",
messagingSenderId: "400034264848",
appId: "1:400034264848:web:f065a4bb76463063dd5795",
measurementId: "G-M5K2EJKLEL"
};
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);
export const auth = getAuth(app);
const db = getFirestore();
export const GetAppointmentsFromFirebase = () => {
const [user, setUser] = useState({});
onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
const appointmentColRef = collection(db, 'users', user?.uid, 'appointments');
let [schedulerData, setSchedulerData] = useState([])
useEffect(() => {
getDocs(appointmentColRef)
.then((snapshot) => {
let appointmentData = []
snapshot.docs.forEach((doc) => {
appointmentData.push({ ...doc.data() })
})
setSchedulerData(appointmentData);
console.log(appointmentData)
})
.catch(err => {
console.log(err.message)
})
}, []);
return schedulerData;
};
You should ideally run those queries only when the user state is loaded and initialize onAuthStateChanged() in useEffect(). Try refactoring the code as shown below:
export const GetAppointmentsFromFirebase = () => {
const [user, setUser] = useState({});
let [schedulerData, setSchedulerData] = useState([])
useEffect(() => {
onAuthStateChanged(auth, async (currentUser) => {
// Check if currentUser is null
if (currentUser) {
setUser(currentUser);
// Read user ID directly from user object
const appointmentColRef = collection(db, 'users', currentUser.uid, 'appointments');
const snapshot = await getDocs(appointmentColRef)
const data = snapshot.docs.map((d) => ({
id: d.id,
...d.data()
}))
setSchedulerData(data);
console.log(data);
} else {
console.log("No user logged in")
}
});
}, []);
return schedulerData;
};

Firebase and React TypeError: firebase.auth(...).onAuthStateChanged is not a function

I am trying to use the onAuthStateChanged trigger to unsubscribe but I keep getting:
Uncaught TypeError: (0 , firebase_util__WEBPACK_IMPORTED_MODULE_0_.getModularInstance)(...).onAuthStateChanged is not a function
below is my Authcontext.js
import React ,{useEffect, useState ,useContext} from 'react';
import { auth } from '../api/firebase';
const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState(null);
const [loading,setLoading] = useState(true);
function register (email,password) {
return auth.createUserWithEmailAndPassword(email,password);
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user);
});
return unsubscribe;
}, []);
const value = {
currentUser,
register,
}
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
You are not subscribing to the StateChange correctly , try the following
React.useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged((user) => { // detaching the listener
if (user) {
// ...your code to handle authenticated users.
} else {
// No user is signed in...code to handle unauthenticated users.
}
});
return () => unsubscribe(); // unsubscribing from the listener when the component is unmounting.
}, []);
I have faced the same error when I was working on react native app and because of firebase version change they have update some methods.
I have resolved this by below code and firebase verison is ^9.6.11.
you do not need to pull onAuthStateChanged
import { getAuth } from "firebase/auth";
export const yourfunc = () => {
const auth = getAuth();
auth.onAuthStateChanged((user) => {
console.log(user)
}
}

How can I go about trying to use onSnapshot in my user context? I would like to get real time updates of notifications (like facebook etc.)

I am trying to get realtime notifications or user profile updates from firebase. I understand I need to use onSnapshot however, I tried before and it wasn't unsubscribing. If you logged out and loggin in as another user, it would sometimes get the profile data of the user logged in previously, which is an AWFUL bug to have.
So basically I have two context providers:
AuthProvider (stores (currentUser) auth email, uid etc.)
UserProvider (stores (userData) firestore data, such as profile picture, username, first name, surname etc.)
How can I go about subscribing to the userData information but then ensuring it unsubscribes when the auth state changes? As I have failed to this at every attempt.
AuthProvider:
import React, { useContext, useState, useEffect } from "react";
import { auth, db } from "../firebase";
const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
function signup(email, password) {
return auth.createUserWithEmailAndPassword(email, password);
}
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password);
}
function logout() {
return auth.signOut();
}
function resetPassword(email) {
return auth.sendPasswordResetEmail(email);
}
function updateEmail(email) {
return currentUser.updateEmail(email);
}
function updatePassword(password) {
return currentUser.updatePassword(password);
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = {
currentUser,
setSearchQueryState,
login,
signup,
logout,
resetPassword,
updateEmail,
updatePassword,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
}
UserProvider:
import React, { useContext, useState, useEffect } from "react";
import { getUserByUserId } from "../services/firebase";
import { useAuth } from "./AuthContext";
const UserContext = React.createContext();
export function useUser() {
return useContext(UserContext);
}
export function UserProvider({ children }) {
const { currentUser } = useAuth();
const [userData, setUserData] = useState();
const [loading, setLoading] = useState(true);
useEffect(async () => {
if (currentUser) {
const data = await getUserByUserId(currentUser.uid);
setUserData(data);
setLoading(false);
} else {
setUserData();
setLoading(false);
}
}, [currentUser]);
const value = {
userData,
};
return (
<UserContext.Provider value={value}>
{!loading && children}
</UserContext.Provider>
);
}
The firebase call I imported into UserProvider
import { auth, db } from "../firebase";
// get user from the firestore where userId === userId (passed from the auth)
export async function getUserByUserId(userID) {
const result = await db.collection("users").doc(userID).get();
const user = result.data();
return user;
}
Thank you in advance!
So currently, when this useEffect in my userAuth is causing me to sometimes log in as other users:
import { auth, db } from "../firebase";
import { getUserByUserId } from "../services/firebase";
import { useAuth } from "./AuthContext";
const UserContext = React.createContext();
export function useUser() {
return useContext(UserContext);
}
export function UserProvider({ children }) {
const { currentUser } = useAuth();
const [userData, setUserData] = useState();
const [loading, setLoading] = useState(true);
//THIS USE EFFECT LISTENS TO SNAPSHOT DATA--------
useEffect(async () => {
let usersnapshot;
if (currentUser) {
usersnapshot = db.collection("users").doc(currentUser.uid);
usersnapshot.onSnapshot((doc) => {
setUserData(doc.data());
setLoading(false);
});
} else {
setLoading(false);
setUserData();
return usersnapshot;
}
}, [currentUser]);
//--------------------------
const value = {
userData,
};
return (
<UserContext.Provider value={value}>
{!loading && children}
</UserContext.Provider>
);
}
I would suggest to detach the listener when the currentUser changes. Can you try to change this part of your code:
useEffect(async () => {
let usersnapshot;
let unsubscribe;
if (currentUser) {
usersnapshot = db.collection("users").doc(currentUser.uid);
unsubscribe = usersnapshot.onSnapshot((doc) => {
setUserData(doc.data());
setLoading(false);
});
} else {
setLoading(false);
setUserData();
return usersnapshot;
}
return unsubscribe;
}, [currentUser]);
What we are doing here that we save the unsubscribe call to a variable in the useEffect and call it when the currentUser changes. That should unsibscribe the onSnapshot each time a user changes.
First, you should use Firestore rules, to deny any request for user data by different users, e.g.:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{user} {
allow read, write: if
request.auth.uid == request.resource.data.ownerID
}
}
}
Second, I am not sure if your unsubscribe call works like this in a useEffect. instead of return unsubscribe; try return () => unsubscribe();

Categories