Every time I'll reload the Profile page, it will redirect itself to the Homepage. How can I fix this?
Once a user has successfully logged in, he or she will be directed to the Homepage. On the Homepage, there's a Profile Page. I can successfully load the Profile page, however, once I'll reload this, the user will be redirected to the homepage again.
//custom hook
export function useAuth() {
const [currentUser, setCurrentUser] = useState();
useEffect(() => {
const unsub = onAuthStateChanged(auth, (user) => setCurrentUser(user));
return unsub;
}, []);
return currentUser;
}
App.js
function App() {
const currentUser = useAuth();
const user = auth.currentUser;
const navigate = useNavigate();
useEffect(() => {
const unsub = onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
const uid = user.uid;
console.log(uid);
navigate("/Homepage");
// ...
} else {
// User is signed out
// ...
navigate("/");
}
});
return unsub;
}, []);
return (
<div>
<div>
<Routes>
{currentUser ? (
<>
<Route
path="/"
element={
<MainLayout>
<LoginPage />
</MainLayout>
}
/>
<Route path="/Homepage" element={<Home />} />
<Route path="/Profile" element={<ProfilePage />} />
</>
) : (
<>
<Route
path="/"
element={
<MainLayout>
<LoginPage />
</MainLayout>
}
/>
</>
)}
</Routes>
</div>
</div>
);
}
export default App;
If I'll console.log(currentUser) this is what it shows:
Also in: https://www.reddit.com/r/reactjs/comments/smfsro/how_to_prevent_the_page_from_redirecting_to/
With Protected Route:
{currentUser ? (
<>
<Route
path="/"
element={
<MainLayout>
<LoginPage />
</MainLayout>
}
/>
<Route path="/Homepage" element={<Home />} />
<Route
path="/Profile"
element={
<PrivateRoute>
<ProfilePage />
</PrivateRoute>
}
/>
</>
) : (
<>
<Route
path="/"
element={
<MainLayout>
<LoginPage />
</MainLayout>
}
/>
</>
)}
PrivateRoute
import React from "react";
import { Navigate, Outlet, useLocation } from "react-router-dom";
import { useAuth } from "../../Firebase/utils";
const PrivateRoute = () => {
const currentUser = useAuth();
// // const !currentUser = null; // determine if authorized, from context or however you're doing it
// // If authorized, return an outlet that will render child elements
// // If not, return element that will navigate to login page
// return currentUser ? <Outlet /> : <Navigate to="/" />;
let location = useLocation();
if (!currentUser) {
console.log(currentUser);
return <Navigate to="/" state={{ from: location }} replace />;
}
};
export default PrivateRoute;
CodePudding user response:
I believe the issue is that currentUser doesn't have a default value, so it's always undefined when you first load the page. You should add a loading state to make sure the user's status is checked before using currentUser to do any logic.
Note: I haven't run any of the code below so there might be some mistakes, but it's just a general idea for reference.
export function useAuth() {
const [isLoading, setIsLoading] = useState(true); // checking the user's status
const [currentUser, setCurrentUser] = useState();
useEffect(() => {
const unsub = onAuthStateChanged(auth, (user) => {
setCurrentUser(user)
setIsLoading(false) // finished checking
});
return unsub;
}, []);
return {currentUser, isLoading};
}
So when you're checking if the user is logged in:
if(!isLoading && currentUser){
// is finished loading and user is logged in
}
I also recommend you store currentUser in a context instead of using a custom hook, since custom hooks won't allow you to share the state between components and it will be mounted every time you use it.
Instead your hook can be used to get the value from the context.
Something like this:
// store currentUser inside context
const AuthContext = createContext();
export const AuthProvider = ({children}) => {
const [isLoading, setIsLoading] = useState(true);
const [currentUser, setCurrentUser] = useState();
useEffect(() => {
const unsub = onAuthStateChanged(auth, (user) => {
setCurrentUser(user)
setIsLoading(false)
});
return unsub;
}, []);
return (
<AuthContext.Provider value={{currentUser, isLoading}}>
{children}
</AuthContext.Provider>
)
}
// read context value with custom hook
export useAuth = () => useContext(AuthContext)
It'll still be the same when you're using useAuth, only that it's getting states from the context instead.
const {currentUser, isLoading} = useAuth()
---Edit to question---
Context is used to share data throughout the application, it's great when you need to use the same data in multiple places. You can store data and pass it down with a provider, and access it with a consumer. I won't go into the details, but you can read more about Context API here: https://reactjs.org/docs/context.html#gatsby-focus-wrapper
About how to use AuthProvider, normally you could wrap it like this in App.js
// App.js
<AuthProvider>
<div>
<Routes>{/* ... */}</Routes>
</div>
</AuthProvider>
But in this case since you're using currentUser in App.js already, you'd have to update AuthProvider to HOC, and wrap it around your component
// AuthContext
export const withAuthProvider = WrappedComponent => ({children}) => {
// ... the rest is the same
}
// App.js
export default withAuthProvider(App);
The reason for this is because context values are only accessible within the scope the provider is wrapped around. Without HOC, your provider is wrapped around your routes, which means the value is not accessible within App.js itself, but is accessible in MainLayout, LoginPage etc.. With HOC, however, since you've wrapped the entire App.js, it can access the values in AuthContext.

