I'm trying to make a simple routing system. Either the user is authenticated or not. I'm trying to use hooks to achieve this but I'm not having the biggest success with it just yet.
authProvider.tsx
import React, {Dispatch, PropsWithChildren, SetStateAction, useContext, useState} from "react";
import { signInWithEmailAndPassword } from "firebase/auth";
import {ref, child, get} from "firebase/database";
import { database, auth } from "../data/firebase";
import { useNavigate } from "react-router-dom";
import { Admin } from "../types/user.type";
interface AuthContext {
user: Admin | undefined;
setUser: Dispatch<SetStateAction<Admin>>;
authenticateUser: (email: string, password: string) => void;
authenticated: boolean;
setAuthenticated: Dispatch<SetStateAction<boolean>>;
}
const AuthContext = React.createContext<AuthContext>({} as AuthContext)
export const useAuth = () => useContext(AuthContext)
export const AuthProvider = ({children}: PropsWithChildren<{}>) => {
const [user, setUser] = useState<Admin>({} as Admin)
const [authenticated, setAuthenticated] = useState<boolean>(false)
const nav = useNavigate();
const authenticateUser = (email: string, password: string):void=> {
signInWithEmailAndPassword(auth, email, password)
.then(async (userCredential) => {
// Signed in
const admin = await fetchUserData(userCredential.user.uid)
console.log(admin)
if (admin?.role !== "Admin") return;
setUser(user);
setAuthenticated(true)
return nav("/")
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
});
}
const fetchUserData = (uid: string): Promise<Admin | null> => {
return new Promise((resolve, reject) => {
const dbRef = ref(database);
get(child(dbRef, `users/${uid}`))
.then((snapshot) => {
if (snapshot.exists()) {
resolve(snapshot.val() as Admin) ;
console.log(snapshot.val());
} else {
reject("Admin not found.")
// console.log("No data available");
}
})
.catch((error) => {
reject("There was an error fetching the admin data.")
});
})
}
return (
<AuthContext.Provider value={{ user, setUser, authenticated, setAuthenticated, authenticateUser }}>
{children}
</AuthContext.Provider>
)
}
App.tsx
import React from "react";
import {Route, Routes, Navigate,BrowserRouter} from "react-router-dom";
import { Home, Login } from "./pages";
import { AuthProvider, useAuth } from "./providers/authProvider";
function App() {
const {authenticated} = useAuth()
return (
<BrowserRouter>
<AuthProvider>
{authenticated
? (
<Routes>
<Route path="/" element={<Home />}/>
</Routes>
)
:(
<Routes>
<Route path="/login" element={<Login />}/>
<Route path="*" element={<Navigate to="/login" />}/>
</Routes>
)
}
</AuthProvider>
</BrowserRouter>
);
}
export default App;
I call authenticateUser
from my login page. The function gets called as expected and the user object is logged in the console. Have I missed something in the documentation which led to authenticated
not being updated in the App.tsx
? It also doesn't refresh the page when calling nav("/")
after setting authenticated
to true
.
CodePudding user response:
You are calling the useAuth()
Hook which consumes the AuthContext
outside of the actual context. In order to get the data from AuthContext
you have to wrap it around <App />
in that case.
const auth = useAuth(); // outside of your authprovider
return (
<AuthProvider> {/* your context definition component */}
{/* You can only access the context INSIDE AuthProvider */}
<ChildrenComponent />
<ChildrenComponent />
<ChildrenComponent />
<ChildrenComponent />
</AuthProvider>
)
do this in your index.tsx
or whereever you call your app.tsx
:
<AuthProvider>
<App /> {/* useAuth() has now access to the context */}
</AuthProvider>