React & Firebase Global State - javascript

I'm using react-firebase-hook , and I'm trying to check if the user is admin or not, and I want to it to be a global state where I don't have to add this code in every and each component to check if the user is admin or not, here is the code..
import { useState, useEffect } from 'react';
import { query, collection, getDocs, where } from "firebase/firestore";
import { auth, db } from "../../config/fbConfig";
import { useAuthState } from "react-firebase-hooks/auth";
const CreateAnn = () => {
const [ann, setAnn] = useState(''); // ignore this
const [admin, setAdmin] = useState(false);
const [user] = useAuthState(auth);
const fetchAdmin = async () => {
try {
const q = query(collection(db, "users"), where("uid", "==", user?.uid));
const doc = await getDocs(q);
const data = doc.docs[0].data();
if(data.admin === true) {
setAdmin(true);
}
else { setAdmin(false); }
} catch (err) {
// do nothing
}
};
useEffect(() => {
fetchAdmin();
});
I want to have this as a global state, tried to useContext but i think I'm using it the wrong way, so anyone can help?

You are correct to use a context, however, you might use it wrong as you said.
You should set up a context that handles the currently logged in user.
In this context you can also fetch the extra details of the user from the user collection.
Also, you can grab the user directly with ID instead of where:
const docRef = doc(db, "users", user.uid);
const docSnap = await getDoc(docRef);
const data = docSnap.exists ? docSnap.data() : undefined
Follow this link to set up the context of auth correct.
https://dev.to/dchowitz/react-firebase-a-simple-context-based-authentication-provider-1ool

Related

Load data from Firestore on page load with useEffect

Simplified code that I am using:
import { useState, useEffect, useContext } from 'react'
import { useRouter } from 'next/router'
import { firestore } from './firebase-config'
import { getDoc, doc } from 'firebase/firestore'
export default function HomePage() {
const router = useRouter()
const user = useContext(AuthContext) // contains user object -> user.user
const [loading, setLoading] = useState(true)
useEffect(() => {
const getData = async() => {
setLoading(true)
const uid = user.user.uid // uid of user in firebase auth
const id = router.query.id // id param of url
const docRef = doc(firestore, `...`)
// doc in a collection that references the above uid and id
const docSnap = await getDoc(docRef)
// get the document from firestore
if (docSnap.exists()) {
importData(docSnap.data()) // add data to store to re-render page
setLoading(false)
} else {
router.push('/main')
// if the user isn't logged in to return to '/'
// ^^ not sure how to do these separately
// if the user is logged in but the document does not exist to return to '/main'
}
}
getData()
}, [router.query, user.user])
return (
<>
{/* */}
</>
)
}
I need to load the document associated with the user's uid and the id param of the currently loaded page, i.e. /main/[id].
These retrieve a Firestore document that is then inserted into the store which causes the HomePage function to re-render to show the data.
uid is found in user.user.uid which is set via onAuthStateChanged in app.js
id is found in router.query.id which is set via useRouter() at the top level
The useEffect() above works, but only temporarily, soon after the data is loaded and the component re-renders, I am linked to '/main' as initially uid and id start as undefined meaning that on the first run of the useEffect hook the else condition is run, it then re-runs as the user and router object is retrieved to load the data, but by the time that has occurred the page is transitioned to './main'.
Would greatly appreciate some help to make this function work.
Additionally, the user should go back to './main' if the document doesn't exist but they are logged in, and if they are not logged in to then be returned to the root ('./')
Thanks in advance!
You can add a loading state for the document retrieval in addition to the loading state that you already have to make sure that the document retrieval is completed before navigating away from the page.
import { firestore } from './firebase-config'
import { getDoc, doc } from 'firebase/firestore'
export default function HomePage() {
const router = useRouter()
const user = useContext(AuthContext) // contains user object -> user.user
const [loading, setLoading] = useState(true)
const [docLoading, setDocLoading] = useState(true)
useEffect(() => {
const getData = async() => {
setLoading(true)
const uid = user.user.uid // uid of user in firebase auth
const id = router.query.id // id param of url
if (!uid) {
setLoading(false)
router.push('/')
return
}
if (!id) {
setLoading(false)
router.push('/main')
return
}
const docRef = doc(firestore, `...`)
// doc in a collection that references the above uid and id
setDocLoading(true)
const docSnap = await getDoc(docRef)
// get the document from firestore
setDocLoading(false)
if (docSnap.exists()) {
importData(docSnap.data()) // add data to store to re-render page
setLoading(false)
} else {
router.push('/main')
}
}
getData()
}, [router.query, user.user])
if (loading || docLoading) {
return <div>Loading...</div>
}
return (
<>
{/* render your component here */}
</>
)
}
So I have managed to fix the issue:
To be able to use uid in the useEffect() hook, onAuthStateChanged is called again rather than using the AuthContext that is created at the top level as this will wait until the user exists
To wait for router.query to be updated you can call router.isReady which returns a Boolean value on whether it has been updated.
Using both of these in this way:
useEffect(() => {
onAuthStateChanged(auth, async (user) => {
if (user) {
if (router.isReady) {
// do stuff -> user exists
} else {
// user exists but the document does not
router.push('/main')
}
}
else {
// user is not logged in
router.push('/')
}
})
}, [router.isReady, router.query])

trying to access a collection with uid, firestore

I'm using react with firebase/firestore, I'm moving from Angular to React but I'm struggling to access the firestore database without AngularFirestore. I am able to login to firestore and obtain the user.uid, I just can't access the 'pred' collection which is nested inside each user.uid so this data is only for the user.
In Angular I access my database like this: (it works)
this.auth.user.pipe(take(1)).subscribe((user) => {
if (user) {
this.items = this.db
.collection("users")
.doc(this.user.uid)
.collection("pred")
.valueChanges();
In react I'm trying to do the same collection, doc, collection, but I can't find clear documentation how to do it.
My react attempt:
import React, { useEffect, useState } from "react";
import { useAuthState } from "react-firebase-hooks/auth";
import { useNavigate } from "react-router-dom";
import "./Dashboard.css";
import { auth, db, logout } from "../../services/firebase/firebase-auth";
import { query, collection, getDocs, where, doc } from "firebase/firestore";
function Dashboard() {
const [user, loading, error] = useAuthState(auth);
const [name, setName] = useState("");
const navigate = useNavigate();
const fetchData = async () => {
const userId = auth.currentUser?.uid;
const docRef = collection(db, "users");
const predictions = collection(db, "pred");
const predSnapshot = await getDocs(predictions);
const predList = predSnapshot.docs.map((doc) => doc.data());
return predList;
};
How can I return the predList which follows:
.collection("users")
.doc(this.user.uid)
.collection("pred")
path order, but using react or just plain javascript? I'm using this import { query, collection, getDocs, where, doc } from "firebase/firestore"; package but I can't see how to do it.
To create a reference to the pred subcollection of the current user in the new modular/v9 syntax:
collection(db, "users", userId!, "pred")
const fetchData = async () => {
const userId = auth.currentUser?.uid;
const docRef = collection(db, "users", userId as string, "pred");
const preds = await getDocs(docRef);
const predList = preds.docs.map((doc) => doc.data());
console.log(predList);
};

Custom Hook Returning Empty Array From Firestore

I'm a newly self-taught coder. Hopefully I'm expressing my situation adequately.
I'm trying to retrieve some data from Cloud Firestore. I've made a custom hook that should useState and setDocuments to the be the retrieved "guide". The query uses useParams to get the id to match to the specific guide I'm trying to get. I think the issue is in the querySnapshot. setDocuments doesn't seem to be working. When I console log "documents" it's an empty array.
Any leads?
import { useParams } from 'react-router-dom'
import { collection, query, where, getDocs } from "firebase/firestore";
import { db } from "../firebase/config"
import { useEffect } from 'react';
export const useGuide = (c) => {
const [documents, setDocuments] = useState([])
const { id } = useParams()
useEffect(() => {
const ref = collection(db, c)
const q = query(ref, where("id", "==", `${id}`))
getDocs(q).then(querySnapshot => {
querySnapshot.forEach((doc) => {
setDocuments(doc.data())
});
});
}, [])
console.log(documents)
return { documents }
}
Here is where I try to use the hook useGuide to set the state which would be passed to a component.
import SingleGuide from '../../components/SingleGuide/SingleGuide'
import { useGuide } from '../../hooks/useGuide'
function Guide() {
const { documents: guides } = useGuide('guides')
console.log(guides)
return (
<div>
{guides && <SingleGuide guide={guides}/>}
</div>
)
}
export default Guide
There are a few issues with your code including setting an array of docs equal to a single doc in the query results. Try something like the following.
import { useParams } from "react-router-dom";
import { collection, query, where, getDocs } from "firebase/firestore";
import { db } from "../firebase/config";
import { useState, useEffect } from "react";
export const useGuide = (c) => {
const [documents, setDocuments] = useState([]);
const { id } = useParams();
useEffect(() => {
// create array to hold all the individual docs
const docs = [];
const ref = collection(db, c);
const q = query(ref, where("id", "==", `${id}`));
getDocs(q).then((querySnapshot) => {
querySnapshot.forEach((doc) => {
docs.push(doc.data());
});
setDocuments(docs);
});
}, [c]); // could consider passing `id` to the hook instead of useParams and adding that as a dependency to update data automatically
console.log(documents);
return { documents };
};

How to properly update user information in firebase?

I have a code that is a form, basically is suppose to update the user information.
According to Firebase documentation manage user but for some reason I'm getting this errors
This is what I have I don't think I'm missing anything is pretty much the same
The only difference is that on my firebase.js I made new cons to make codes shorter but that shouldn't affect at all.
firebase.js
const db = firebase.firestore();
const auth = firebase.auth();
const storage = firebase.storage();
export {db, auth, storage};
Code
import {auth} from "./firebase"
const authUser = auth.currentUser
const update = (e) => {
e.preventDefault();
authUser.updateProfile({
displayName: {fullName}
})
}
I just wanted to test it on the user name first and then do all the others but I don't understand why is not working is super simple.
Update
authUser is undefined. This means that auth.currentUser was undefined at the time when it was assigned to authUser. This probably means that the user is not logged in. You will need to handle the case when the user is not logged in, like
import {auth} from "./firebase"
const authUser = auth.currentUser
const update = (e) => {
e.preventDefault();
if (authUser) {
authUser.updateProfile({
displayName: fullName
})
} else {
//User is not logged in, handle that case here
}
}
import {auth} from "./firebase";
const authUser = auth.currentUser;
const update = (e) => {
e.preventDefault();
authUser?.updateProfile({
displayName: fullName
});
}

React Native Firebase Authentication with Hooks loses userState on app resume

I'm currently creating a React Native mobile application with Typescript.
The application uses the Firebase authentication with the Google OAuth Provider.
In order to use the username and some other details (retrieved from Firestore) I'm using a React Provider like shown in the following example:
import React, {useState, useEffect} from 'react';
import auth from '#react-native-firebase/auth';
import { GoogleSignin } from '#react-native-community/google-signin';
import firestore from '#react-native-firebase/firestore';
GoogleSignin.configure({
webClientId: 'x.googleusercontent.com',
});
const getUserById = async (id: string) => {
const admin = await firestore().collection("users").doc(id).collection("priv").doc("admin").get();
const prot = await firestore().collection("users").doc(id).collection("priv").doc("protected").get();
const jsonData = {
admin: admin.data(),
protected: prot.data(),
};
return jsonData;
}
const AuthContext = React.createContext({});
function AuthProvider(props: any) {
const [user, setUser] = useState(auth().currentUser);
const [details, setDetails] = useState({});
const [initializing, setInitializing] = useState(true);
const onAuthStateChanged = async (authUser: any) => {
setUser(authUser);
if (authUser !== null)
refreshDetails();
}
const refreshDetails = async () => {
const details = (await getUserById(user.uid));
setDetails(details);
}
useEffect(() => {
const subscriber = auth().onAuthStateChanged(onAuthStateChanged);
return subscriber; // unsubscribe on unmount
}, []);
const loginWithGoogle = async () => {
const { idToken } = await GoogleSignin.signIn();
// Create a Google credential with the token
const googleCredential = auth.GoogleAuthProvider.credential(idToken);
// Sign-in the user with the credential
return auth().signInWithCredential(googleCredential);
}
const logout = () => {
auth()
.signOut()
}
return (
<AuthContext.Provider value={{user, loginWithGoogle, logout, refreshDetails, details, initializing}} {...props}></AuthContext.Provider>
)
}
const useAuth = () => {
const state = React.useContext(AuthContext);
return {
...state,
};
}
export {AuthProvider, useAuth};
As you can see in the example I'm using this useEffect method from React to subscribe to authentication changes.
Unfortunately if I close the app and reopen it again, this authentication change isn't triggered so the user state isn't set and I get a bunch of errors.
What would be the best practice in a scenario like this? I think I only need to trigger the onAuthStateChangeEvent when the app was started again.
Thanks for all help
IJustDev
onAuthStateChanged function must be triggered when the app re-opens. However, it's supposed to run asynchronously you have to implement the case user's value is invalid.

Categories