Firebase getAuth().currentUser returns null - reactjs

I am trying to get my current user from getAuth() but it returns null.
import React, { useState, useEffect } from 'react'
import { getAuth } from 'firebase/auth'
const Profile = () => {
const [user, setUser] = useState(null)
const auth = getAuth()
useEffect(() => {
console.log(auth.currentUser)
}, [])
return <h1>Profile</h1>
}
export default Profile

Related

Why is implementing authentication on my react app not working

I'm trying to implement authentication in a react app using the context API and Firebase's auth service and I'm halfway through, except for the fact that I'm not getting the desired results.
For example, if I click on the submit button without typing anything, it's supposed to throw an auth error of invalid email and show a post error message to firebase in the console. The same goes for if I type a password that's less than 6 characters. But, as it is, nothing is working. No error's showing in the console either, so, I can't seem to figure out where the problem is. I attached my app.js and UserAuthContext.js files below.
My SignUp code
import { useState } from "react";
import { useUserAuth } from "./context/UserAuthContext";
import { UserAuthContextProvider } from "./context/UserAuthContext";
function SignUp() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError ] = useState("")
const {signUp} = useUserAuth ();
const handleSubmit = async (e) => {
e.preventDefault();
try{
await signUp(email, password);
} catch (err){
}
}
// React code omitted
}
and my UserAuthContext.js file:
import { createContext, useContext, useState, useEffect } from "react";
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
} from "firebase/auth";
import { auth } from "../firebase";
const userAuthContext = createContext();
export function UserAuthContextProvider({ children }) {
const [user, setUser] = useState("");
function signUp(email, password) {
return createUserWithEmailAndPassword(email, password);
}
function login(email, password) {
return signInWithEmailAndPassword(auth, email, password);
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
return () => {
unsubscribe();
};
}, []);
return (
<userAuthContext.Provider value={{ user, signUp }}>
{children}
</userAuthContext.Provider>
);
}
export function useUserAuth() {
return useContext(userAuthContext);
}
I attached my firebase.js file, just in case
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
// Settings for init app
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export default app;
After some questions, I think I understand your problem, when calling the createUserWithEmailAndPassword function, you are forgetting to pass the auth instance as the first parameter.
I suggest you make the following changes to the SignUp.
Note that I am using the function directly from firebase and not from the React Context.
import {
auth
} from "../firebase";
import {
createUserWithEmailAndPassword,
} from "firebase/auth";
const handleSubmit = async (e) => {
e.preventDefault();
if (email && password) {
createUserWithEmailAndPassword(auth, email, password)
.then(() => console.log("Created!"))
.catch((err) => console.log("Error!", err))
}
}

toggle the value returned by a usecontext hook

Working on a nextjs app using typescript w/ a firestore backend.
I currently am using my own useContext hook to make globally available a firestore user object, an array of 'business entity IDs' (strings) that the user is authorized to view, as well as an array of objects containing each biz's document data.
Because a user can be a member of multiple business entities, I'd like to be able to have them toggle between a 'current business' object whose document ID would be used to pass into further queries as well as document add / update functions.
I'm just not sure how to go about trigger the setting and switching of this 'current biz' id.
I'm assuming I'd want to somehow set the value in another usecontext hook so that the value is globally available to use around the app?
Below is my current custom context hooks doc
please let me know if you'd need to see any more data
import { auth, firestore } from "../lib/firebase";
import { useContext, useEffect, useState } from "react";
import { useAuthState } from "react-firebase-hooks/auth";
import {
doc,
onSnapshot,
collection,
query,
where,
getDocs,
} from "firebase/firestore";
import { BizContext } from "./context";
//types
// Custom hook to read auth record and user profile doc
export function useUserData() {
const [user, loading, error] = useAuthState(auth);
const [username, setUsername] = useState(null);
useEffect(() => {
// turn off realtime subscription
let unsubscribe;
//this actively listens to a the users collection
if (user) {
const userDocRef = doc(firestore, "users", user.uid);
unsubscribe = onSnapshot(userDocRef, (doc) => {
setUsername(doc.data()?.username);
});
} else {
setUsername(null);
}
return unsubscribe;
}, [user]);
return { user, username };
}
export function useBelongsTo() {
const [user] = useAuthState(auth);
const [belongsTo, setBelongsTo] = useState<[]>([]);
const [bizInfo, setBizInfo] = useState<[]>([]);
useEffect(() => {
let unsubscribe;
if (user) {
const bizColRef = collection(firestore, "businesses");
const q = query(bizColRef, where("admins", "array-contains", user.uid));
unsubscribe = onSnapshot(q, (querySnapshot) => {
let bizId: [] = [];
let bizInfo: [] = [];
querySnapshot.forEach((doc) => {
bizId.push(doc.id);
bizInfo.push(doc.data());
});
setBelongsTo(bizId);
setBizInfo(bizInfo);
});
} else {
setBelongsTo([]);
console.log("no businesses, no user");
}
return unsubscribe;
}, [user]);
return { belongsTo, bizInfo };
}
_app.tsx
import "../styles/globals.css";
import type { AppProps } from "next/app";
import { ChakraProvider } from "#chakra-ui/react";
import customTheme from "../extendTheme";
import { Toaster } from "react-hot-toast";
import { BizContext, UserContext } from "../lib/context";
import { useBelongsTo, useUserData } from "../lib/hooks";
import Layout from "../components/layout/Layout";
function MyApp({ Component, pageProps }: AppProps) {
const userData = useUserData();
const bizData = useBelongsTo();
return (
<ChakraProvider theme={customTheme}>
<UserContext.Provider value={userData}>
<BizContext.Provider value={bizData}>
<Layout>
<Component {...pageProps} />
<Toaster />
</Layout>
</BizContext.Provider>
</UserContext.Provider>
</ChakraProvider>
);
}
export default MyApp;
context.tsx
import React from "react";
export const UserContext = React.createContext({});
export const BizContext = React.createContext({});
If you want to make belongsTo and bizInfo globally for later usage, I recommend you to use react Context instead of using hooks. Move the logic to the context like the sample below.
import { useContext, createContext, useEffect, useState } from "react";
export const AuthContext = createContext();
export function AuthContextProvider({ children }) {
const [user] = useAuthState(auth);
const [belongsTo, setBelongsTo] = useState<[]>([]);
const [bizInfo, setBizInfo] = useState<[]>([]);
const [currentBizId, setCurrentBizId] = useState();
useEffect(() => {
let unsubscribe;
if (user) {
const bizColRef = collection(firestore, "businesses");
const q = query(bizColRef, where("admins", "array-contains", user.uid));
unsubscribe = onSnapshot(q, (querySnapshot) => {
let bizId: [] = [];
let bizInfo: [] = [];
querySnapshot.forEach((doc) => {
bizId.push(doc.id);
bizInfo.push(doc.data());
});
setBelongsTo(bizId);
setBizInfo(bizInfo);
});
} else {
setBelongsTo([]);
console.log("no businesses, no user");
}
return unsubscribe;
}, [user]);
function switchBusinessEntity(id){
let currentBusinessEntity=belongsTo.find(biz=>biz.id === id);
setCurrentBizId(currentBusinessEntity);
}
return <AuthContext.Provider
value={{
belongsTo,
bizInfo,
currentBizId,
switchBusinessEntity
}}>
{children}
</AuthContext.Provider>;
}
export const useAuthContext = () => useContext(AuthContext);
And in the _app.js, wrapper the App with AuthContextProvider
import { AuthContextProvider } from "#/contexts/authContext";
function MyApp({ Component, pageProps }) {
return <AuthContextProvider>
<Component {...pageProps} />
</AuthContextProvider>;
}
export default MyApp;
And in the component, you can access the global states like this:
function YourComponent(){
const {
belongsTo,
bizInfo,
currentBizId,
switchBusinessEntity
} = useAuthContext()
return <select onChange={e => switchBusinessEntity(e.target.value)>
{belongsTo.map((bizId, index) => <option key={index} value={bizId.value}>{bizId}</option>)}
</select>
}

auth is not a function

I'm trying to do a simple login form, and Firebase auth give me this error
TypeError: firebase__WEBPACK_IMPORTED_MODULE_1_["default"].auth is
not a function. (In
'firebase__WEBPACK_IMPORTED_MODULE_1_["default"].auth()',
'firebase__WEBPACK_IMPORTED_MODULE_1_["default"].auth' is undefined)
Somebody have the solution?
import React, {useEffect, useState} from "react"
import app from '../firebase'
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
useEffect(() => {
app.auth().onAuthStateChanged(setCurrentUser);
}, []);
return (
<AuthContext.Provider value={{currentUser}}>
{children}
</AuthContext.Provider>
)
}
const useAuth = () => useContext(AuthContext);
export { useAuth, AuthProvider };
add these two lines at the end also

NextJS - Protected route not working creating the following error (Unexpected token o in JSON at position 1 at JSON.parse (<anonymous>))

Trying to implement protected routes but there is an error that occurs at the AuthUserProvider component. What I'm hoping to do is pass the firebase user information via the routes so I can send the user to the login page if they're not signed in. What is happening is that the user file is returning null even when a sign-in is performed.
Here is the error text:
Uncaught SyntaxError: Unexpected token o in JSON at position 1
at JSON.parse ()
at getUserFromCookie (userCookies.js:9)
at useUser.js:47
I've called the user data here and then sent it through to the children. Or I think I have. Here is the code in the AuthUserProvider file that i've attempted:
import { createContext, useContext, Context } from "react";
import useUser from "../firebase/useUser";
const authUserContext = createContext({
user: null,
});
export function AuthUserProvider({ children }) {
const auth = useUser();
return (
<authUserContext.Provider value={auth}>{children}</authUserContext.Provider>
);
}
// custom hook to use the authUserContext and access authUser and loading
export const useAuth = () => useContext(authUserContext);
Here is the useUser code where I have set up to call the user information via a token:
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import firebase from "firebase/app";
import "firebase/auth";
import initFirebase from "./initFirebase";
import {
removeUserCookie,
setUserCookie,
getUserFromCookie,
} from "./userCookies";
import { mapUserData } from "./mapUserData";
initFirebase();
export default function useUser() {
const [user, setUser] = useState();
const router = useRouter();
const logout = async () => {
return firebase
.auth()
.signOut()
.then(() => {
// Sign-out successful.
router.push("/auth");
})
.catch((e) => {
console.error(e);
});
};
useEffect(() => {
// Firebase updates the id token every hour, this
// makes sure the react state and the cookie are
// both kept up to date
const cancelAuthListener = firebase.auth().onIdTokenChanged((user) => {
if (user) {
const userData = mapUserData(user);
setUserCookie(userData);
setUser(userData);
} else {
removeUserCookie();
setUser();
}
});
const userFromCookie = getUserFromCookie();
if (!userFromCookie) {
router.push("/");
return;
}
setUser(userFromCookie);
return () => {
cancelAuthListener();
};
}, []);
return { user, logout };
}
Here is the getUseFromCookie & setUserCookie code:
import cookies from "js-cookie";
export const getUserFromCookie = () => {
const cookie = cookies.get("auth");
if (!cookie) {
return;
}
return JSON.parse(cookie);
};
export const setUserCookie = (user) => {
cookies.set("auth", user, {
expires: 1 / 24,
});
};
export const removeUserCookie = () => cookies.remove("auth");

Test component with context and react hook

I have a simple Dashboard component that relies on React context to manage auth. It contains a custom hook useAuth to extract the current user as well as the auth related functions: login, logout, etc.
This is the Context file: AuthContext.js:
import React, { createContext, useContext, useState, useEffect } from "react";
import { auth } from "../config/firebase";
const AuthContext = 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();
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = {
currentUser,
signup,
login,
logout,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
}
This is the Dashboard.js component:
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import { useAuth } from "../context/AuthContext";
export default function Dashboard() {
const { currentUser, logout } = useAuth();
const [error, setError] = useState("");
const history = useHistory();
const handleLogout = async () => {
setError("");
try {
await logout();
history.push("/login");
} catch (e) {
setError(e.message);
}
};
return (
<div>
{error && <p>{error}</p>}
<h1>This is the Dashboard</h1>
<h5>Email: {currentUser.email}</h5>
<button onClick={handleLogout} type="button">
Logout
</button>
</div>
);
}
As recommened by React Testing Library I have created a test-utils.js file:
import React, { createContext } from "react";
import { render } from "#testing-library/react";
import { BrowserRouter as Router } from "react-router-dom";
const AuthContext = createContext();
const currentUser = {
email: "abc#abc.com",
};
const signup = jest.fn();
const login = jest.fn();
const logout = jest.fn();
const AllTheProviders = ({ children }) => {
return (
<Router>
<AuthContext.Provider value={{ currentUser, signup, login, logout }}>
{children}
</AuthContext.Provider>
</Router>
);
};
const customRender = (ui, options) => {
render(ui, { wrapper: AllTheProviders, ...options });
};
export * from "#testing-library/react";
export { customRender as render };
However, when running Dashboard.test.js I get error
TypeError: Cannot destructure property 'currentUser' of '((cov_5mwatn2cf(...).s[0]++) , (0 , _AuthContext.useAuth)(...))' as it is undefined.
4 |
5 | export default function Dashboard() {
> 6 | const { currentUser, logout } = useAuth();
| ^
7 | const [error, setError] = useState("");
8 | const history = useHistory();
import React from "react";
import Dashboard from "./Dashboard";
import { act, render, screen } from "../config/test-utils-dva";
beforeEach(async () => {
await act(async () => {
render(<Dashboard />);
});
});
test("displays dashboard", () => {
expect(screen.getByText(/dashboard/i)).toBeInTheDocument();
});
I think it is because Dashboard component is trying to use useAuth from AuthContext.js, how can I force the rendered Dashboard component to use the mocked data that I am sending in the test-utils.jsfile?
Instead of creating a new context, use the AuthContext from context/AuthContext for <AuthContext.Provider>, as that's the context that the hook uses.
So, in AuthContext.js, export the context instance:
export const AuthContext = createContext();
Then, in your test-util.js file, instead of again calling createContext (which will create a completely separate context instance - the contexts are not the same even if they are stored in a variable with the same name!), just import the previously exported instance:
import { AuthContext } from "../context/AuthContext";
const AllTheProviders = ({ children }) => {
return (
<Router>
<AuthContext.Provider value={{ currentUser, signup, login, logout }}>
{children}
</AuthContext.Provider>
</Router>
);
};

Resources