Logout and Redirect after signOut - reactjs

I am trying to log out the user and then redirect to HomePage.
I have done the below signOut function from firebase 9.0 but the user comes as null.
Ideally would like to log out and remove session data or log out.
import React, { useContext, createContext, useEffect, useState } from 'react';
import { auth, db } from '../utils/init-firebase';
import {
confirmPasswordReset,
createUserWithEmailAndPassword,
GoogleAuthProvider,
onAuthStateChanged,
sendPasswordResetEmail,
signInWithEmailAndPassword,
signInWithGoogle,
signInWithPopup,
signOut,
} from 'firebase/auth';
import {
query,
collection,
onSnapshot,
forEach,
getDoc,
getDocs,
} from 'firebase/firestore';
const AuthContext = createContext({
// currentUser: null,
signInWithGoogle: () => Promise,
login: () => Promise,
register: () => Promise,
logout: () => Promise,
forgotPassword: () => Promise,
resetPassword: () => Promise,
});
export const useAuth = () => useContext(AuthContext);
export default function AuthContextProvider({ children }) {
const [currentUser, setCurrentUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [subscription, setSubscription] = useState(null);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setCurrentUser(user ? user : null);
setIsLoading(false);
localStorage.setItem('user', true);
const uid = auth.currentUser?.uid;
});
});
});
return () => {
unsubscribe();
};
}, []);
const [uniqueId, setUniqueId] = useState(null);
useEffect(() => {
setUniqueId(currentUser);
}, []);
// console.log('user id from Auth Context==> ', currentUser);
function login(email, password) {
return signInWithEmailAndPassword(auth, email, password);
}
function logout() {
return signOut(auth);
}
const value = {
currentUser,
signInWithGoogle,
login,
register,
logout,
forgotPassword,
resetPassword,
isLoading,
subscription,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

Related

The context api resets and re-evaluates from start when I go to another profile route/ next page

AuthContext.js
import { createContext, useEffect, useState } from "react";
import { axiosInstance } from "../../axiosConfig";
import { useCustomToast } from "../../customHooks/useToast";
const initialState = {
user: null,
isLoggedIn: false,
login: () => null,
logOut: () => null,
};
export const AuthContext = createContext(initialState);
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const { showToast } = useCustomToast();
console.log("i am rinning agaon here");
const checkLogin = async () => {
try {
const res = await axiosInstance.get("/auth/refresh");
setIsLoggedIn(true);
console.log("the user is", res?.data);
setUser(res?.data?.user);
} catch (e) {
console.log(e);
setIsLoggedIn(false);
}
};
const logOutHandler = async () => {
try {
const res = await axiosInstance.get("/auth/logout");
showToast(res?.data?.message);
} catch (e) {
showToast("Something went wrong.Please try again");
}
};
useEffect(() => {
checkLogin();
}, []);
const login = (userData) => {
setUser(userData);
setIsLoggedIn(true);
};
const logOut = () => {
setUser(null);
logOutHandler();
setIsLoggedIn(false);
};
return (
<AuthContext.Provider
value={{
user,
isLoggedIn,
login,
logOut,
}}
>
{children}
</AuthContext.Provider>
);
};
ProtectedRoute.js
import React, { useEffect, useContext } from "react";
import { useRouter } from "next/router";
import { AuthContext } from "../context/authContext";
const ProtectedRoute = ({ children }) => {
const { isLoggedIn } = useContext(AuthContext);
const router = useRouter();
useEffect(() => {
if (!isLoggedIn) {
router.push("/login");
}
}, [isLoggedIn]);
return <>{isLoggedIn && children}</>;
};
export default ProtectedRoute;
I am using NextJS and context api for managing user state. Here at first I will check for tokens and if it is valid I will set loggedIn state to true. But suppose I want to go to profile page which is wrapped by protected route, what is happening is AuthContext is resetting and evaluating itself from beginning, the isLoggedIn state is false when I go to /profile route. If I console log isLoggedIn state inside protectedRoute.js, it is false at start and before it becomes true, that router.push("/login) already runs before isLoggedIn becomes true. It feels like all AuthContext is executing again and again on each route change. Is there any code problem? How can I fix it? The one solution I have found is wrapping that wrapping that if(!loggedIn) statement with setTimeOut() of 1 secs so that until that time loggedIn becomes true from context API

UseContext returns null in getServer side props NextJS

I have used the context in other places, such as login, database functions, and more. However, when I try to run functions or variables inside my context in places such as custom api's or getServerSideProps, it returns the following error, TypeError: Cannot read properties of null (reading 'useContext'). I am attaching my auth context, my initialization of the context, and the getServerSideProps function that is returning an error
_app.js
import RootLayout from '../components/Layout'
import { AuthProvider } from '../configs/auth-context'
import '../styles/globals.css'
export default function App({ Component, pageProps }) {
return (
<AuthProvider >
<RootLayout>
<Component {...pageProps} />
</RootLayout>
</AuthProvider>
)}
auth-context
import React, { useContext, useState, useEffect, useRef } from 'react'
import { auth, db, provider } from './firebase-config'
import { GoogleAuthProvider, signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut, onAuthStateChanged, signInWithPopup } from 'firebase/auth'
import { doc, getDoc, setDoc } from 'firebase/firestore'
import {useRouter} from 'next/router';
const AuthContext = React.createContext({currentUser: {uid: "TestUid", email:"Testeremail#email.com"}})
export function UseAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const router = useRouter();
const [currentUser, setCurrentUser] = useState({uid: "TestUid", email:"Testeremail#email.com"})
const [loading, setLoading] = useState(true)
async function signup(email, password) {
createUserWithEmailAndPassword(auth, email, password)
.then(async (result) => {
const user = result.user;
await userToDb(user);
router.push('/portfolio');
return user;
}).catch((error) => {
console.error(error);
})
return
}
async function login(email, password) {
return signInWithEmailAndPassword(auth, email, password)
.then(async (result) => {
const user = result.user;
await userToDb(user);
router.push('/portfolio');
return user;
}).catch((error) => {
console.error(error)
})
}
function logout() {
router.push('/')
return signOut(auth)
}
async function googleSignIn() {
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider)
.then(async (result) => {
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential.accessToken;
// The signed-in user info.
const user = result.user;
await userToDb(user);
router.push('/portfolio');
return user
}).catch((error) => {
console.log(error)
// const errorCode = error.code;
// const errorMessage = error.message;
// The email of the user's account used.
// const email = error.customData.email;
// The AuthCredential type that was used.
// const credential = GoogleAuthProvider.credentialFromError(error);
} )
}
const userToDb = async (user) => {
// await setDoc(doc(db, "users", user.uid), {
// userEmail: user.email,
// userID: user.uid
// }, {merge: false})
let currentRef = doc(db, 'users', user.uid)
let currentUserID = user.uid;
let currentEmail = user.email;
await setDoc(currentRef, {
userEmail: currentEmail,
userID: currentUserID
}, {merge: false})
}
function fixData(docs) {
console.log("this works")
// setDocuments(docs);
let retMap = new Map();
if (currentUser !== null) {
docs?.map(function(doc) {
console.log(doc)
let tic = doc.stockTicker
let data = {
shares: doc.shares,
price: doc.price,
type: doc.type
}
if(!retMap.has(tic)) {
retMap.set(tic, [data]);
console.log(tic + " " + data)
// setMap(new Map(datamap.set(tic, {shares: shares, averagePrice: price})))
}
else {
let x = retMap.get(tic);
x.push(data);
}
})
console.log(retMap)
return retMap;
}
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async user => {
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
const value = {
currentUser,
login,
signup,
logout,
googleSignIn,
fixData
}
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
)
}
getServerSideProps
export async function getServerSideProps() {
let allDocs = []
let avgDocs = []
const {currentUser} = UseAuth()
return {
props: {allDocs, avgDocs}
}
}
I don't know the correct answer, but hooks should be used in components and hooks without exception to ssr.

LocalStorage values are one login behind

I'm having this problem with my AuthContext from a React app. I check the localStorage and the values stored are from the last user logged, so I have to login twice to get the corrent info.
This is the Context code. And the 'useLocalStorage' function. I guess in the useMemo is the problem but I haven't been able to solve it. Is this a bad approach?
import { createContext, useContext, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useLocalStorage } from "./useLocalStorage";
import jwtDecode from "jwt-decode";
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [token, setToken] = useLocalStorage("accessToken", null);
const [userId, setUserId] = useLocalStorage("currentUserId", null);
const [userEmail, setUserEmail] = useLocalStorage("currentUserEmail", null);
const [userRole, setUserRole] = useLocalStorage("currentUserRole", null);
const [user, setUser] = useState({});
const navigate = useNavigate();
const login = async (credentials) => {
return fetch("https://localhost:7264/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(credentials),
})
.then((res) => res.json())
.then((res) => {
setToken(res);
console.log(res);
const tokenToDecode = localStorage.getItem("accessToken");
setUser(jwtDecode(tokenToDecode));
setUserId(user.sub);
setUserEmail(
user[
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
]
);
setUserRole(
user["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]
);
});
};
const logout = () => {
setToken(null);
setUserId(null);
setUserEmail(null);
setUserRole(null);
navigate("/", { replace: true });
};
const value = useMemo(
() => ({
token,
login,
logout,
userId,
userEmail,
userRole,
}),
[token]
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export const useAuth = () => {
return useContext(AuthContext);
};
import { useState } from "react";
export const useLocalStorage = (keyName, defaultValue) => {
const [storedValue, setStoredValue] = useState(() => {
try {
const value = localStorage.getItem(keyName);
if (value) {
return JSON.parse(value);
} else {
localStorage.setItem(keyName, JSON.stringify(defaultValue));
return defaultValue;
}
} catch (err) {
return defaultValue;
}
});
const setValue = (newValue) => {
try {
localStorage.setItem(keyName, JSON.stringify(newValue));
} catch (err) {}
setStoredValue(newValue);
};
return [storedValue, setValue];
};

Blank page after refresh react firebase authentication

I have the above firebase db. I want to extract the displayName value and use it in a greeting message after the user is successfully login (e.g. Hello George!). I manage to achieve this but when I refresh the page everything disappears and in console I get this error "index.esm2017.js:1032 Uncaught TypeError: Cannot read properties of undefined (reading 'indexOf')".
Is this a problem of how I extract the displayName from firebase document?
Can someone explain to me what is the problem, please?
Here is my code:
AuthContext.js
import { createContext, useContext, useEffect, useState } from "react";
import {
onAuthStateChanged,
signInWithEmailAndPassword,
signOut,
} from "firebase/auth";
import { auth } from "../utils/firebase/firebase.utils";
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState({});
const signIn = (email, password) =>
signInWithEmailAndPassword(auth, email, password);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
console.log(currentUser);
setUser(currentUser);
});
return () => unsubscribe();
}, []);
const logOut = () => signOut(auth);
return (
<UserContext.Provider value={{ user, signIn, logOut }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
return useContext(UserContext);
};
WelcomePage.jsx
import React, { useState, useEffect } from "react";
import { UserAuth } from "../contexts/AuthContext";
import { db } from "../utils/firebase/firebase.utils";
import { doc, Firestore, getDoc } from "firebase/firestore";
const WelcomePage = () => {
const [userDetails, setUserDetails] = useState({});
const { user } = UserAuth();
useEffect(() => {
const docRef = doc(db, "users", user.uid);
const fetchData = async () => {
try {
const docSnap = await getDoc(docRef);
setUserDetails(docSnap.data());
console.log(docSnap.data());
} catch (e) {
console.log(e);
}
};
fetchData();
}, [user]);
return (
<div>
<h1>Hello, {userDetails.displayName}!</h1>
</div>
);
};
export default WelcomePage;
You might only want to fetch documents in the WelcomePage component if there's a truthy uid value to use.
const { user } = UserAuth();
useEffect(() => {
const fetchData = async () => {
const docRef = doc(db, "users", user.uid);
try {
const docSnap = await getDoc(docRef);
setUserDetails(docSnap.data());
console.log(docSnap.data());
} catch (e) {
console.log(e);
}
};
if (user?.id) {
fetchData();
}
}, [user]);

"Objects are not valid as a React child" when adding user data to Firestore

I want to know that how can we save user data during signup in Firestore.
import {
createUserWithEmailAndPassword,
onAuthStateChanged,
signInWithEmailAndPassword,
signOut,
} from "firebase/auth";
import { collection, setDoc, doc } from "firebase/firestore";
import React, { useContext, useEffect, useState } from "react";
import { auth, db } from "../firebaseConfig";
const AuthContext = React.createContext();
export const useAuth = () => {
return useContext(AuthContext);
};
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
const signup = async (email, password) => {
return createUserWithEmailAndPassword(auth, email, password).then(
(cred) => {
setDoc(doc(collection(db, "users", cred.user.uid)), {
name: "manan",
city: "chd",
});
}
);
};
const login = (email, password) => {
return signInWithEmailAndPassword(auth, email, password);
};
const logout = () => {
return signOut(auth);
};
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = {
currentUser,
login,
signup,
logout,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};
as you can see I have created an AuthContext.js for all the functions. now, when in signup() I am not able to store the user data corresponding to their UID... as I have used
setDoc(doc(collection(db, "users", cred.user.uid)), {
name: "manan",
city: "chd",
});
but it is not working at all it's throwing an error
error:
Objects are not valid as a React child (found: object with keys {error}). If you meant to render a collection of children, use an array instead.
you are using both a document and a collection as a reference. you should only use a document reference on a setDoc
from:
setDoc(doc(collection(db, "users", cred.user.uid)), {
name: "manan",
city: "chd",
});
change to:
setDoc(doc(db, "users", cred.user.uid), {
name: "manan",
city: "chd",
});

Resources