0

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.

1
  • Hav you tried console.log() inside the .catch of authenticateUser()? Maybe there's an error in the request and you're not handling it. Also try to simply do nav("/") instead of returning it. Commented Jul 29, 2022 at 11:26

1 Answer 1

3

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>
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for pointing this out. I must have read over this. For the future reader that has the same issue. Make sure you also navigate back to whichever route you'd like to redirect to.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.