I am using react router dom RouterProvider which decouples fetching from rendering, from the official remix-run react router example of auth-router-provider, it was stated in the README.md
that
we can no longer rely on React context and/or hooks to get our user authentication status. We need access to this information outside of the React tree so we can use it in our route loader and action functions. https://github.com/remix-run/react-router/tree/dev/examples/auth-router-provider
I created a standalone object outside of the React tree that manages my authentication state for login and logout
, my object structure looks like
export const authProvider: AuthProvider = {
user: null,
async login(email: string, password: string) {
return signInWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
authProvider.user = userCredential.user;
return redirect("/dashboard");
})
.catch(() => {
return toast.error("Password or Email invalid");
});
},
async signout() {
signOut(auth);
authProvider.user = null;
},
};
I import the standalone object my loader file and return authProvider.user
in my dashboard loader
loader() {
return { user: authProvider.user };
}
the user is access using the useRouteLoaderData
in the dashboard component
const {user} = useRouteLoaderData("dashboard")
the user will be initially null and I can navigate else where if the user is not logged in, but when a user is logged in the user is no longer null and can view the dashboard page, but when i refresh the dashboard page after a user login the user always return null.
I can't seem to get right where i will use the onAuthStateChange
from firebase because i was using it inside a useEffect
earlier when i was managing user with react context.
I need assistnace on how to make the user persist on refresh of the dashboard page.
Update your authProvider
object to include a function that can call Firebase's onAuthStateChanged
function to check the current user's authentication status. Wrap the call in a Promise that resolves when the onAuthStateChanged
succeeds, or rejects in the failure case.
import {
getAuth,
onAuthStateChanged,
signInWithEmailAndPassword,
signOut,
} from "firebase/auth";
const auth = getAuth();
interface AuthProvider {
user: User | null;
checkAuth(): Promise<void>;
login(email: string, password: string): Promise<void>;
signout(): Promise<void>;
}
export const authProvider: AuthProvider = {
user: null,
checkAuth: async () => {
return new Promise((resolve, reject) => {
onAuthStateChanged(
auth,
(user) => {
authProvider.user = user; // User | null
resolve();
},
reject
);
});
},
login: async (email: string, password: string) => {
return signInWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
authProvider.user = userCredential.user;
return redirect("/dashboard");
})
.catch(() => {
return toast.error("Password or Email invalid");
});
},
signout: async () => {
signOut(auth);
authProvider.user = null;
},
};
Call your authProvider
instance's checkAuth
function and wait for it to settle.
loader = async () => {
try {
await authProvider.checkAuth();
return {
user: authProvider.user
};
} catch(error) {
// handle/ignore error
}
}