This question already has an answer here:
React-Router-Dom unable to render page but routes back due to PrivateRoute
(1 answer)
Closed 8 months ago.
My problem is that the moment i navigate to the homepage and the user is not authenticated the page shows for a split second and then move on to the login page. I want it to redirect to login only and not show the homepage for a split second
I already created a private route in my project but for a split second the protected routes shows when i navigate on to it.
here is my code:
AuthContextProvider
const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
return () => unsubscribe();
}, []);
const value = { user };
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
PrivateRoute.js
const PrivateRoute = ({ children }) => {
let { user } = useAuth();
if (user) {
return <Outlet />;
} else {
return <Navigate to="/login" />;
}
};
App.js
function App() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/" element={<Layout />}>
<Route element={<PrivateRoute />}>
<Route path="/" element={<Home />} />
<Route path="/home" element={<Home />} />
<Route path="/reminders" element={<Reminders />} />
<Route path="/archive" element={<Archive />} />
<Route path="/trash" element={<Trash />} />
</Route>
</Route>
</Routes>
);
}
Loginpage.js
const LoginPage = () => {
const { user } = useAuth();
const navigate = useNavigate();
const signIn = async () => {
const provider = new GoogleAuthProvider();
await signInWithRedirect(auth, provider);
};
useEffect(() => {
onAuthStateChanged(auth, (currentUser) => {
if (currentUser) {
navigate("/");
}
});
}, []);
return (
<>
<button
onClick={signIn}
className="bg-blue-500 p-3 text-white hover:bg-blue-600"
>
Sign In With Google
</button>
</>
);
};
Code for hindering the Loggedin User to go to the Login Route
I have created a SpecialRoute which will redirect the loggedIn User to the mainpage if the user tries to go the login page.
SpecialRoute.js
import { Login } from '../pages/Login';
import { useAuth } from '../firebase-config';
import React from 'react';
import { Outlet, Navigate } from 'react-router-dom';
// import { useAuth } from '../firebase-config';
export const SpecialRoute = () => {
const user = useAuth();
return user ? <Outlet /> : <Navigate to="/" replace />;
};
App.js
<Route element={<SpecialRoute />}>
<Route path="/login" element={<Login />} />
</Route>
In your Private Route Component, do this :-
const PrivateRoute = ({ children }) => {
let { user } = useAuth();
return typeof user === 'undefined' ? (
<h1>Loading.....</h1>
) : user ? (
<Outlet />
) : (
<Navigate to="/login" />
);
};
Below is how I created my private routes (in Firebase v9):-
useAuth Hook
// custom hook
export function useAuth() {
//
const [currentUser, setCurrentUser] = useState<any>();
useEffect(() => {
const unSubscribe = onAuthStateChanged(auth, (user) =>
setCurrentUser(user)
);
return unSubscribe;
}, []);
return currentUser;
}
My PrivateRoute/ProtectedRouted Component
import { Login } from '../pages/Login';
import React from 'react';
import { Outlet } from 'react-router-dom';
import { useAuth } from '../firebase-config';
export const ProtectedRoute = () => {
const user = useAuth();
console.log('/////user autheticated', user);
return typeof user === 'undefined' ? (
<h1>Loading.....</h1>
) : user ? (
<Outlet />
) : (
<Login />
);
};
App.js
function App() {
return (
<>
<Router>
<Routes>
<Route element={<ProtectedRoute />}>
<Route path="/" element={<Home />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signin />} />
</Routes>
</Router>
</>
);
}
Related
I'm using React and firebase, where I have an admin, and there I generate a token, which I can already register in the database. So I wanted to copy this generated url, for example: localhost:5173/avaliacao/a57078f588e, where a57078f588e is the generated token id for each survey
So the user would access the Rating page only if this token is valid, otherwise it would go to a 404 page.
I'm trying, however everything I try the undefined token.
Here is my App.jsx
export function App() {
return (
<Router>
<Routes>
<Route element={<PrivateRoutes />}>
<Route element={<Admin />} path="/admin" />
<Route element={<Dashboard />} path="/dashboard" />
<Route element={<Collaborator />} path="/collaborators" />
<Route element={<Service />} path="/services" />
</Route>
<Route para element={<TokenRoutes />}>
<Route element={<Rating />} path="/avaliacao/:token" />
<Route element={<Thanks />} path="/thanks" />
<Route element={<NotFound />} path="*" />
</Route>
<Route element={<Login />} path="/" />
</Routes>
</Router>
);
}
And here is my TokenRoutes:
import { Navigate, Outlet } from "react-router-dom";
import { useToken } from "../hooks/useToken";
export function TokenRoutes() {
const { token } = useToken();
console.log(token);
return token != "undefined" ? <Outlet /> : <Navigate to="/notfound" />;
}
And my Rating page:
export function Rating() {
const { token } = useToken();
console.log(token);
let { id } = useParams();
return (
<div className="containerRating">
<span className="login100-form-title p-b-48">
<i className="zmdi zmdi-font"></i>
<img src={imgLogo} alt="Imagem" />
</span>
Param: {id}
<Form />
</div>
);
}
My useToken:
import { addDoc, collection, getDocs } from "firebase/firestore";
import { createContext, useContext, useEffect, useState } from "react";
import { toast } from "react-toastify";
import { uid } from "uid";
import { db } from "../services/firebaseConfig";
const TokenContext = createContext();
export function TokenProvider({ children }) {
const [tokens, setTokens] = useState([]);
const [token, setToken] = useState();
const tokensCollectionRef = collection(db, "tokens");
useEffect(() => {
const getTokens = async () => {
const data = await getDocs(tokensCollectionRef);
setTokens(
data.docs.map((doc) => ({
...doc.data(),
}))
);
};
getTokens();
}, []);
async function generateToken() {
await addDoc(tokensCollectionRef, {
id: uid(),
createdAt: new Date().toString(),
expiredIn: new Date(
new Date().setDate(new Date().getDate() + 7)
).toString(),
used: false,
})
.then(() => {
toast.success("Avaliação gerada com sucesso!", {
theme: "colored",
});
console.log("Gerado no firebase 🔥");
})
.catch((error) => {
toast.error("Ocorreu um erro ao gerar", {
theme: "colored",
});
console.log(error.message);
});
}
return (
<TokenContext.Provider value={{ generateToken, token, tokens }}>
{children}
</TokenContext.Provider>
);
}
export function useToken() {
const context = useContext(TokenContext);
return context;
}
In order not to put all the code here, any other doubt, follow the complete code on github:
https://github.com/eltonsantos/evaluation-system-react
Resuming:
I just want to get the URL generated by me containing the token, share it with the user and the user was able to access it, but if he changes the url it gives a 404 page. url that I shared the token comes undefined
I am trying to send users to different routes based on the roles of the user which is stored in the realtime firebase database, but I am getting the following error:
App.js:36 Uncaught (in promise) TypeError: Cannot read properties of null (reading 'users')
Following is my App.js file where I am making the call for the firebase data"
App.js
`
import React from "react";
import { Route, Routes, Navigate } from "react-router-dom";
import Landing from "./components/Landing";
import PhoneDetails from "./components/PhoneDetails";
import Home from "./components/Home/App.jsx";
import Signup from "./components/Signup";
import SignIn from "./components/Signin";
import { auth } from "./firebase-config.js";
import { useEffect } from "react";
import FirebaseData from "./firebaseData";
function App() {
document.body.style = "background: #F8F5FA;";
// getting the user data from firebase
const firebaseData = FirebaseData();
const [displayName, setDisplayName] = React.useState("");
const [isAuthenticated, setIsAuthenticated] = React.useState(false);
const [role, setRole] = React.useState("");
useEffect(() => {
auth.onAuthStateChanged((user) => {
if (user) {
// User is signed in
// ...
setIsAuthenticated(trEue);
setDisplayName(user.displayName);
// ERROR ON THIS LINE
setRole(firebaseData.users[user.uid].role)
// setRole(firebaseData.users[user.uid].role);
} else {
// User is signed out
// ...
setIsAuthenticated(false);
setDisplayName("");
setRole("");
}
});
}, []);
console.log("role:", role);
return (
<Routes>
<Route
path="/"
exact
element={
<Home isAuthenticated={isAuthenticated} displayName={displayName} role={role}/>
}
/>
<Route path="/signup" element={<Signup />} />
<Route path="/signin" element={<SignIn />} />
{
isAuthenticated && role === "admin" ? (
<Route path="/home" element={<Landing />} />
) : (
<Route
path="/"
element={
<Home isAuthenticated={isAuthenticated} displayName={displayName} />
}
/>
)
}
{isAuthenticated && role === "admin" ? (
<Route path="/details" element={<PhoneDetails />} />
) : (
<Route
path="/"
element={
<Home isAuthenticated={isAuthenticated} displayName={displayName} />
}
/>
)}
<Route path="/" element={<Navigate replace to="/" />} />
<Route path="*" element={<Navigate replace to="/" />} />
</Routes>
);
}
export default App;
`
In my App.js I am calling the FirebaseData() file which is given below:
firebaseData.js
`
import {database} from "./firebase-config";
import React from "react";
import {ref, onValue} from "firebase/database";
import {useEffect} from "react";
const db = database;
export default function FirebaseData() {
const [data, setData] = React.useState(null);
useEffect(() => {
onValue(ref(db), (snapshot) => {
setData(snapshot.val());
});
}, []);
return data;
}
`
The data in the firebase DB is stored in the following format:
users
---->uid
------>roles
I've tried to find the solution for this but couldn't find any. Any help will be appreciated!
check firebaseData is defined in the useEffect and check if users exists using ? operator
useEffect(() => {
if(firebaseData){
auth.onAuthStateChanged((user) => {
if (user) {
// User is signed in
// ...
setIsAuthenticated(trEue);
setDisplayName(user.displayName);
// ERROR ON THIS LINE
setRole(firebaseData.users?.[user.uid]?.role)
// setRole(firebaseData.users[user.uid].role);
} else {
// User is signed out
// ...
setIsAuthenticated(false);
setDisplayName("");
setRole("");
}
});
}
}, [firebaseData]);
My react app has the following routes and contexts:
const App = () =>
<AuthContextProvider>
<IntelContextProvider>
<Routes>
<Route path="/" element={<Layout />}>
<Route path="login" element={<Authentication />} />
<Route path="register" element={<Registration />} />
<Route element={<RequireAuth />}>
<Route path="/" element={<Home />} />
</Route>
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</IntelContextProvider>
</AuthContextProvider>
Authentication uses an access token stored in memory (AuthContextProvider) and a refresh token stored in a HttpOnly cookie.
My home path uses a protected route implemented as follows:
export const RequireAuth = () => {
const location = useLocation()
const {auth} = useAuth()
return (auth?.username ?
<Outlet/> :
<Navigate to='/login' state={{from: location}} replace/> )
}
If the user is not authenticated, it is redirected to /login without any issue. However, I need to do the opposite too:
If the user has a valid refresh token, when the page is refreshed or the user requests '/register' or '/login pages', then I want the route to be redirected to Home component again.
I tried to put do the following in the Authentication component:
const Authentication = () => {
const [authenticated, setAuthenticated] = useState(null)
const {values, errors, handleChange, handleSubmit} = useAuthenticationForm(validate)
const silentlySignIn = useSilentSignIn()
useEffect(() => {
const silentlyLogin = async () => {
const isAuthenticated = await silentlySignIn()
if(isAuthenticated) setAuthenticated(true)
}
silentlyLogin()
// eslint-disable-next-line
}, [])
return (
authenticated ? <Navigate to='/'/> :
<main className="authentication">
<form>
...
</form>
</main>
);
}
Here is my AuthContext:
export const AuthContextProvider = ({ children }) => {
const [auth, setAuth] = useState()
return (
<AuthContext.Provider value={{ auth, setAuth }}>
{children}
</AuthContext.Provider>
)
}
And Here is my useSignIn hook:
const useSignIn = () => {
const [success, setSuccess] = useState(false)
const [error, setError] = useState()
const { setAuth } = useAuth()
const signIn = async (payload) => {
try {
setError(null)
const { headers: { authorization: token }, data: { uuid } } = await axiosPrivate.post(`/login`, payload)
setAuth({ token, uuid, username: payload.username })
setSuccess(true)
} catch (error) {
console.log(error.message)
if (!error.response) setError('Sistema temporariamente indisponível.')
else if (error.response.status === 401) setError('Usuário ou senha inválidos.')
else setError('Algo deu errado.')
}
}
return [signIn, success, error]
}
Here is my useSilentSignIn (to get a new access token if the refresh token is still valid):
const useSilentSignIn = () => {
const { auth, setAuth } = useAuth()
const silentlySignIn = async () => {
try {
if (auth?.uuid) return false
const response = await axiosPrivate.get('/refresh-token')
const token = response.headers.authorization
const uuid = response.data.uuid
const username = response.data.username
setAuth(prev => ({ ...prev, token, uuid, username }))
return true
} catch (error) {
console.log('Logged out. Please sign in.')
return false
}
}
return silentlySignIn
}
I "solved" the problem, but it first renders the login, then navigates to '/' (due to React component lifecycle). It does not seem like a good solution, it is ugly, and I would need to do the same for '/register' or any similar route.
How to implement something efficient for such a problem?
Github of the project: https://github.com/lucas-ifsp/CTruco-front
Thanks
Your authenticated state has three possible states (yay JavaScript):
Authenticated (true)
Non-authenticated (false)
Not yet known (null)
You could convert them to string enums for clarity, but for conciseness, this is how you would handle all three cases:
if (authenticated === null) return <Spinner /> // Or some other loading indicator
return (
authenticated ? <Navigate to='/'/> :
<main className="authentication">
<form>
...
</form>
</main>
);
There's no need to add this logic to the Authentication rendered on the "/login" path. In this case you create another route protection component that does the inverse of the RequireAuth component. This is commonly referred to as an "anonymous route" that you only want users that are not authenticated to access.
If the user is authenticated then render a redirect to any non-anonymous path, otherwise render the outlet for the nested route to render its element into. While the auth status is being checked and still undefined, you can render null or any sort of loading indicator to make the route protection wait until the state value updates.
Example:
export const AnonymousRoute = () => {
const { auth } = useAuth();
if (auth === undefined) {
return null; // or loading indicator/spinner/etc...
}
return auth.username
? <Navigate to='/' replace />
: <Outlet/>;
}
...
<Routes>
<Route element={<Layout />}>
<Route element={<AnonymousRoute />}>
<Route path="login" element={<Authentication />} />
<Route path="register" element={<Registration />} />
</Route>
<Route element={<RequireAuth />}>
<Route path="/" element={<Home />} />
</Route>
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
I managed to make a private route and navigate to different pages using react-router-dom. How ever, when I navigate to a page and reload it, it first goes to /login for half a second and the reloads the page correctly. How can I prevent this unwanted behavior and improve my routing?
Here are my routes:
<Router>
<Route
path="/"
component={() =>
!auth ? <Redirect to="/login" /> : <Redirect to={path} />
}
/>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
<Route exact path="/login" component={RedirectPage} />
</Router>
This is the full component:
import {
Route,
BrowserRouter as Router,
Link,
Redirect,
} from "react-router-dom";
import { Container, Button } from "#material-ui/core/";
import Login from "./Login";
import { useContext,useState } from "react";
import { UserContext } from "../App";
import { signOut } from "../Storage/Auth";
const Routes = () => {
const { auth, setAuth, logging } = useContext(UserContext);
const [path,setPath] = useState("/home")
const handleSignOut = () => {
signOut(setAuth);
console.log("Auth", auth);
};
const Home = () => {
console.log("Home");
return (
<Container>
<h1>Welcome</h1>
<Link to="/">
<Button onClick={handleSignOut}> Log Out</Button>
</Link>
<Link to="/dashboard">
<Button> Dash</Button>
</Link>
</Container>
);
};
const Dashboard = () => {
setPath("/dashboard")
console.log("Dash");
return (
<Container>
<Link to="/home">
<Button> HOME</Button>
</Link>
<h1>Dashboard</h1>
</Container>
);
};
const RedirectPage = () => {
if (!logging) {
return <div></div>;
} else {
return <Login />;
}
};
return (
<Router>
<Route
path="/"
component={() =>
!auth ? <Redirect to="/login" /> : <Redirect to={path} />
}
/>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
<Route exact path="/login" component={RedirectPage} />
</Router>
);
};
export { Routes };
This is my Login component.
import { useState, useContext } from "react";
import {
Button,
Card,
Container,
Typography,
Box,
TextField,
} from "#material-ui/core/";
import { useHistory} from "react-router-dom";
import { signIn } from "../Storage/Auth";
import { UserContext } from "../App";
const Login = () => {
const [mail, setMail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const { user, setUser } = useContext(UserContext);
const handleSignIn = async (m: string, p: string) => {
await signIn(m, p).then((e) => {
console.log("USERID", e, user);
setUser(e);
});
};
const history = useHistory();
const handleEnter = () => {
history.push("/home");
};
const handleOnKey = (e: any) => {
if (e.key === "Enter") {
e.preventDefault();
handleSignIn(mail, password);
handleEnter();
}
};
return (
<Card className="Card" raised={true}>
<Container className="Input">
<Typography className="Sign-in" paragraph={true} variant="inherit">
Sign in
</Typography>
<Box
className="Box"
borderColor="error.main"
border={2}
borderRadius="borderRadius"
>
<Container>
<TextField
fullWidth={true}
placeholder=" email"
value={mail}
onChange={(e) => {
setMail(e.target.value);
}}
onKeyDown={(e) => {
handleOnKey(e);
}}
/>
</Container>
</Box>
</Container>
<Container className="Input">
<Box
className="Box"
borderColor="error.main"
borderRadius="borderRadius"
border={2}
>
<Container>
<TextField
fullWidth={true}
placeholder=" password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
type="password"
onKeyDown={(e) => {
handleOnKey(e);
}}
/>
</Container>
</Box>
<h1> </h1>
<Button
onClick={() => {
handleSignIn(mail, password);
}}
fullWidth={true}
color="primary"
variant="contained"
type="submit"
>
Sign In{" "}
</Button>
<h1> </h1>
<Box className="Sign-in">
<Button size="small"> Register </Button>
</Box>
<h1> </h1>
</Container>
</Card>
);
};
export default Login;
This is the App component:
import { useEffect } from "react";
import { Routes } from "./Routing/Routes";
import "./App.css";
import { Container } from "#material-ui/core/";
import initFirebase from "./Storage/Secret";
import { useState, createContext } from "react";
import { onAuthChange } from "./Storage/Auth";
export const UserContext = createContext<any>(null);
function App() {
const [user, setUser] = useState(null);
const [auth, setAuth] = useState<string | null>("");
const [logging, setLogging] = useState(null)
useEffect(() => {
initFirebase();
}, []);
useEffect(() => {
onAuthChange(setAuth,setLogging);
}, [auth]);
return (
<UserContext.Provider value={{ user, setUser, auth,setAuth,logging }}>
<div className="App">
<Container>
<Routes />
</Container>
</div>
</UserContext.Provider>
);
}
export default App;
Also, here is the auth logic:
import firebase from "firebase/app";
import "firebase/auth";
const auth = () => firebase.auth();
const signIn = async (email, password) => {
await auth()
.signInWithEmailAndPassword(email, password)
.then((userCredential) => {
var user = userCredential.user;
console.log("USER", user);
return user.uid;
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
alert(errorCode, errorMessage);
return null;
});
};
const onAuthChange = (setState, setLoading) => {
auth().onAuthStateChanged((u) => {
if (!u) {
console.log(u);
setLoading(true);
} else {
setState(u);
setLoading(false);
}
});
};
const signOut = (setState) => {
auth()
.signOut()
.then(function () {
console.log("LOGGED OUT");
})
.catch(function (error) {
console.log("ERROR LOGGING OUT");
});
setState(null);
};
export { signIn, signOut, onAuthChange }
Finally, the full code is in https://gitlab.com/programandoconro/adminkanjicreator
Any suggestion will be appreciated, thanks.
I would recommend doing the auth check earlier. So something like this so that the routes themselves only get rendered if there is something in auth. I think your example is also missing the Switch statement which often helps.
<Router>
{!auth ? (
<Switch>
<Route exact path="/login" component={RedirectPage} />
</Switch>
) : (
<Switch>
<Route exact path="/home" component={Home} />
<Route exact path="/dashboard" component={Dashboard} />
</Switch>
)}
</Router>
Typically you will want some sort of "loading" or "indeterminant" state to represent neither authenticated nor unauthenticated. You can use this third "state" to hold the UI before committing to rendering one way or the other on anything based upon authentication.
Since your auth logic resolves to a boolean true|false.
const onAuthChange = (setState, setLoading) => {
auth().onAuthStateChanged((u) => {
if (!u) {
console.log(u);
setLoading(true);
} else {
setState(u);
setLoading(false);
}
});
};
You can use the fact that the initial auth state is neither of these. I suggest using null.
const [auth, setAuth] = useState<string | null>(null);
When rendering the Route utilizing the auth state you can augment the logic to return early before deciding to redirect.
<Route
path="/"
render={() => {
if (auth === null) return null;
return <Redirect to={auth ? path : "/login" />;
}}
/>
Note here that I've also switched over to the render prop, the component prop is intended for attaching actual React components. These are treated a little differently. You can read about the route render method differences here.
The full router example:
<Router>
<Switch>
<Route path="/home" component={Home} />
<Route path="/dashboard" component={Dashboard} />
<Route path="/login" component={RedirectPage} />
<Route
path="/"
render={() => {
if (auth === null) return null;
return <Redirect to={auth ? path : "/login" />;
}}
/>
</Switch>
</Router>
Note here that I've also included the Switch component and reordered the routes so the more specific paths are listed before less specific paths. This allows you to remove the unnecessary exact prop from all the routes since the Switch renders routes exclusively (versus inclusively as the Router does).
I finally managed to solve the issue. Now the reload works perfectly and the security was implemented as excepted. This is my final Router:
<Router>
<Route
path="/"
render={() =>
logging ? <Redirect to={"/login"} /> : <Redirect to={path} />
}
/>
<Route exact path="/" render={() => auth && <Home />} />
<Route exact path="/dashboard" render={() => auth && <Dashboard />} />
<Route exact path="/login" component={Login} />
</Router>
This is how the component looks like now.
import {
Route,
BrowserRouter as Router,
Link,
Redirect
} from "react-router-dom";
import { Container, Button } from "#material-ui/core/";
import Login from "./Login";
import { useContext, useState, useEffect } from "react";
import { UserContext } from "../App";
import { signOut } from "../Storage/Auth";
const Routes = () => {
const { auth, setAuth, logging } = useContext(UserContext);
const handleSignOut = () => {
signOut(setAuth);
console.log("Auth", auth);
};
const pathname = window.location.pathname;
const [path, setPath] = useState(pathname);
useEffect(() => {
console.log(path);
path === "/login" && setPath("/");
path !== "/" && path !== "/dashboard" && setPath("/");
}, [auth]);
const Home = () => {
console.log("Home");
return (
<Container>
<h1>Welcome</h1>
<Link to="/">
<Button onClick={handleSignOut}> Log Out</Button>
</Link>
<Link to="/dashboard">
<Button> Dash</Button>
</Link>
</Container>
);
};
const Dashboard = () => {
console.log("Dash");
return (
<Container>
<Link to="/">
<Button> HOME</Button>
</Link>
<h1>Dashboard</h1>
</Container>
);
};
return (
<Router>
<Route
path="/"
render={() =>
logging ? <Redirect to={"/login"} /> : <Redirect to={path} />
}
/>
<Route exact path="/" render={() => auth && <Home />} />
<Route exact path="/dashboard" render={() => auth && <Dashboard />} />
<Route exact path="/login" component={Login} />
</Router>
);
};
export { Routes };
Thanks #Richard and #Drew for their kind support.
Whenever I go the the path ("/") even if I am logged in, Signup component is loaded once for few second then Dashboard is shown same happens with redirecting how to solve it.
<Route
exact
path="/"
render={() => {
return loggedIn ? <Dashboard /> : <Signup />;
}}
/>
<Route
exact
path="/resetPassword"
render={() => {
return loggedIn ? (
<Redirect to="/" />
) : (
<ResetPassword/>
);
}}
/>
and I'm setting the login like this:
const cookieCheck = () => {
const mt = Cookies.get("rt");
if (mt === "" || mt === undefined) {
if (loggedIn) {
setLogin(false);
}
} else {
if (!loggedIn) {
setLoggedIn(true);
}
}
};
My complete code is as follows
import React, { Fragment, useState, useEffect, lazy, Suspense } from 'react';
import { Paper } from '#material-ui/core';
import Dashboard from './Components/Dashboard/Dashboard';
import LoadingPage from './Components/LoadingPage/main';
import ForgotPassword from './Components/ForgotPassword/Fp';
import Signup from './Components/Signup/Signup';
import Login from './Components/Login/Login';
import PageNotFound from './Components/404/Page.js';
import Activation from './Components/Activation/Activation';
import ResetPassword from './Components/ResetPassword/Reset';
import {
BrowserRouter as Router,
Route,
Switch,
Redirect,
} from 'react-router-dom';
import Cookies from 'js-cookie';
const App = () => {
const [login, setLogin] = useState(false);
const checkStatus = function () {
const mt = Cookies.get('t');
if (mt === '' || mt === undefined) {
setLogin(false);
} else {
setLogin(true);
// return true;
}
};
useEffect(() => {
const interval = setInterval(() => {
cookieCheck();
}, 1000);
return () => clearInterval(interval);
});
const cookieCheck = async () => {
const mt = await Cookies.get('rt');
if (mt === '' || mt === undefined) {
if (login) {
setLogin(false);
}
} else {
if (!login) {
setLogin(true);
}
}
};
console.log(login);
return (
<Router>
<Fragment>
<Paper elevation={0}>
<Switch>
<Route
path="/forgotpassword"
exact
component={ForgotPassword}
/>
<Route
exact
path="/login"
render={() => {
return login ? <Redirect to="/" /> : <Login />;
}}
/>
<Route
exact
path="/signup"
render={() => {
if (login) {
return <Redirect to="/" />;
} else {
return <Signup />;
}
}}
/>
<Route
exact
path="/"
render={() => {
return login ? (
<Dashboard />
) : (
<LoadingPage />
);
}}
/>
<Route
exact
path="/resetPassword/:code/:uid"
render={() => {
return login ? (
<Redirect to="/" />
) : (
ResetPassword
);
}}
/>
<Route
exact
path="/activation/:code/:uid"
component={Activation}
/>
<Route component={PageNotFound} />
</Switch>
</Paper>
</Fragment>
</Router>
);
};
export default App;
You need try something like this to add loading state to your component:
import React, { Fragment, useState, useEffect, lazy, Suspense } from "react";
import { Paper } from "#material-ui/core";
import Dashboard from "./Components/Dashboard/Dashboard";
import LoadingPage from "./Components/LoadingPage/main";
import ForgotPassword from "./Components/ForgotPassword/Fp";
import Signup from "./Components/Signup/Signup";
import Login from "./Components/Login/Login";
import PageNotFound from "./Components/404/Page.js";
import Activation from "./Components/Activation/Activation";
import ResetPassword from "./Components/ResetPassword/Reset";
import {
BrowserRouter as Router,
Route,
Switch,
Redirect,
} from "react-router-dom";
import Cookies from "js-cookie";
const App = () => {
const [login, setLogin] = useState(false);
const [loading, setLoading] = useState(true);
const checkStatus = function () {
const mt = Cookies.get("t");
if (mt === "" || mt === undefined) {
setLogin(false);
} else {
setLogin(true);
}
setLoading(false);
};
useEffect(() => {
const interval = setInterval(() => {
checkStatus();
}, 1000);
return () => clearInterval(interval);
}, []);
console.log(login);
const isLoginComponent = login ? <Dashboard /> : <LoadingPage />;
return (
<Router>
<Fragment>
<Paper elevation={0}>
<Switch>
<Route path="/forgotpassword" exact component={ForgotPassword} />
<Route
exact
path="/login"
render={() => {
return login ? <Redirect to="/" /> : <Login />;
}}
/>
<Route
exact
path="/signup"
render={() => {
if (login) {
return <Redirect to="/" />;
} else {
return <Signup />;
}
}}
/>
<Route
exact
path="/"
render={() => {
return loading ? <div>loading</div> : isLoginComponent;
}}
/>
<Route
exact
path="/resetPassword/:code/:uid"
render={() => {
return login ? <Redirect to="/" /> : ResetPassword;
}}
/>
<Route exact path="/activation/:code/:uid" component={Activation} />
<Route component={PageNotFound} />
</Switch>
</Paper>
</Fragment>
</Router>
);
};
export default App;
Read more about how cookies work here: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/cookies/get
I think this issue is you don't immediately check the cookie status when the component mounts. This coupled with login initial state false will render any login ? true : false false branch component until the first cookie check that returns valid authentication.
const [login, setLogin] = useState(false);
const checkStatus = function () {
const mt = Cookies.get('t');
if (mt === '' || mt === undefined) {
setLogin(false);
} else {
setLogin(true);
}
};
useEffect(() => {
const interval = setInterval(() => {
checkStatus(); // <-- not called for 1000ms
}, 1000);
return () => clearInterval(interval);
});
Solution
Need to also immediately invoke the cookie check function. I suggest also starting with a non-true/false state value so you know when the initial check is complete.
const [login, setLogin] = useState(null);
const checkStatus = function () {
const mt = Cookies.get('t');
if (mt === '' || mt === undefined) {
setLogin(false);
} else {
setLogin(true);
}
};
useEffect(() => {
const interval = setInterval(() => {
checkStatus();
}, 1000);
checkStatus(); // <-- also invoke first render
return () => clearInterval(interval);
});
Check for login === null and return null (or any pending/loading UI really), otherwise return the regular ternary.
<Route
exact
path="/"
render={() => {
if (login === null) return null;
return login ? (
<Dashboard />
) : (
<LoadingPage />
);
}}
/>