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 />
);
}}
/>
Related
I'm trying to build an authentication service using react context with react router v6 routes and I can´t go to the expected route. So, my login in service is working, I'm retrieving the user from the back and setting in localStorage, also have a state from context to store the user data (loggedUser), with only id and type at the moment. Probably is something silly but the loggedUser is not updated in the routes file and I don't understand why, and if it's not the context than it's the routing, with the "Warning: Cannot update a component (BrowserRouter) while rendering a different component (ClientRoute).". Thanks in advance, for any help!
Here is the code that I'm using:
AuthContext.jsx file (don't know if used the useEffect correctly with storing the user too)
import React, { createContext, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import user_api from "../Middles/user_api";
export const AuthContext = createContext({});
//Context criado para autenticação, definir permissão de usuário para determinadas rotas
export const AuthProvider = ({ children }) => {
const navigate = useNavigate();
const [loggedUser, setLoggedUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const cachedUser = localStorage.getItem("user");
if(!cachedUser){
console.log("no user");
} else {
console.log("user logged ", cachedUser);
}
if(loggedUser){
localStorage.setItem("user", JSON.stringify(loggedUser));
setIsLoading(false);
navigate('../home', { replace: true });
}
},[loggedUser]);
const login = async (loginValues) => {
try {
const response = await user_api.loginUser(loginValues);
console.log(response.data);
const { userData, token } = response.data;
const user = {
id: userData.id,
type: userData.data.type
}
setLoggedUser(user);
} catch (error) {
console.log(error);
}
}
const logout = () => {
console.log("logout");
}
return(
<AuthContext.Provider value={ {isAuthenticated: !!loggedUser, loggedUser, isLoading, login, logout} }>
{children}
</AuthContext.Provider>
)
}
Routes file (App_router in index.jsx)
import React, { useContext, useState } from 'react';
import { AuthContext, AuthProvider } from '../Contexts/AuthContext';
import {
BrowserRouter as Router,
Route,
Routes,
useNavigate
} from "react-router-dom";
import HomePage from '../Pages/HomePage';
import Exercises from '../Pages/Exercises';
import UserForm from '../Pages/Forms/UserForm';
import Login from '../Pages/Login';
const App_Router = () => {
const navigate = useNavigate();
const { isAuthenticated, loggedUser, isLoading } = useContext(AuthContext);
const ProRoute = ({ children }) => {
const cachedUser = JSON.parse(localStorage.getItem("user"));
if(cachedUser && cachedUser.type == 'Profissional'){
return children;
}
if(isLoading){
return <section>Carregando...</section>;
}
if(isAuthenticated || loggedUser?.type == "Profissional"){
return children;
}
else {
console.log("Usuário não autorizado.");
navigate('/login', {replace: true});
}
}
const ClientRoute = ({ children }) => {
const cachedUser = JSON.parse(localStorage.getItem("user"));
console.log("cachedUser ", cachedUser);
if(cachedUser && cachedUser.type == 'Profissional'){
return children;
}
if(isLoading){
return <section>Carregando...</section>;
}
if(isAuthenticated || loggedUser?.type == "Cliente"){
return children;
}
else {
console.log("Usuário não autorizado.");
navigate('/login', {replace: true});
}
}
return (
<AuthProvider>
<Routes>
<Route index path='/' element={<Login />} />
<Route exact path='/login' element={<Login/>}/>
<Route exact path='/register' element={<UserForm/>}/>
<Route
path='/home/Profissional'
element={
<ProRoute>
<HomePage/>
</ProRoute>
}/>
<Route
path='/home'
element={
<ClientRoute>
<HomePage/>
</ClientRoute>
}/>
<Route
path='/home/Profissional/exercises'
element={
<ProRoute>
<Exercises/>
</ProRoute>
}/>
</Routes>
</AuthProvider>
)
}
export default App_Router;
Login file
import React, { useContext, useState } from 'react'
import { Link } from 'react-router-dom';
import { AuthContext } from '../../Contexts/AuthContext';
import user_api from '../../Middles/user_api';
import './styles.css'
//Componente de Login usado para autenticação e condicionamento de rotas, autenticação de usuário
const Login = () => {
const { login } = useContext(AuthContext);
const [values, setValues] = useState({
email: '',
password: '',
usertype: ''
});
const handleValues = (e) => {
setValues({...values, [e.target.name]: e.target.value});
}
const handleSubmit = async (e) => {
e.preventDefault();
login(values)
.then(
res => {
setValues({
email: '',
password: '',
usertype: ''
});
})
}
return (
<main id="login-wrapper">
<aside>Chamada de API de treino</aside>
<section id="login-container">
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input type="email" name='email' value={values.email} onChange={handleValues}/>
<label htmlFor="password">Senha</label>
<input type="password" name='password' value={values.password} onChange={handleValues}/>
Esqueceu a senha?
<fieldset>
<legend>Tipo:</legend>
<input type="radio" name='usertype' checked={values.usertype === 'Profissional'} value='Profissional' onChange={handleValues} />
<label htmlFor="usertype">Profisisonal</label>
<input type="radio" name='usertype' checked={values.usertype === 'Cliente'} value='Cliente' onChange={handleValues} />
<label htmlFor="">Aluno</label>
</fieldset>
<button type='submit'>Entrar</button>
</form>
</section>
<aside>
<h4>Ainda não possui conta?</h4>
<Link to="/register">Cadastre-se</Link>
</aside>
</main>
)
}
export default Login;
Here is the full error log that pops when I try to login:
react_devtools_backend.js:4026 Warning: Cannot update a component (`BrowserRouter`) while rendering a different component (`ClientRoute`). To locate the bad setState() call inside `ClientRoute`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
at ClientRoute (http://localhost:8888/src/Routes/index.jsx:55:5)
at RenderedRoute (http://localhost:8888/node_modules/.vite/deps/react-router-dom.js?v=f7b60e3a:2434:5)
at Routes (http://localhost:8888/node_modules/.vite/deps/react-router-dom.js?v=f7b60e3a:2744:5)
at AuthProvider (http://localhost:8888/src/Contexts/AuthContext.jsx:22:3)
at App_Router (http://localhost:8888/src/Routes/index.jsx:26:20)
Please help, I'm relative new to react and this would be much appreciated. Thanks!
The main issue with the code is that the protected route components are calling navigate as an unintentional side-effect, i.e. outside the useEffect hook. They should either call navigate from the useEffect hook or as a more conventional method just render the Navigate component. A secondary issue that may or may not be contributing to any issues is that both the ProRoute and ClientRoute components are being declared inside another React component; this is generally considered an anti-pattern.
Declare the route protection components on their own and rewrite them to render the Navigate component and consume the AuthContext.
App_Router
import {
BrowserRouter as Router,
Route,
Routes,
Navigate,
Outlet,
} from "react-router-dom";
const ProRoute = ({ children }) => {
const { isAuthenticated, loggedUser, isLoading } = useContext(AuthContext);
if (isLoading) {
return <section>Carregando...</section>;
}
if (isAuthenticated || loggedUser?.type == "Profissional") {
return children || <Outlet />;
} else {
return <Navigate to='/login' replace />;
}
}
const ClientRoute = ({ children }) => {
const { isAuthenticated, loggedUser, isLoading } = useContext(AuthContext);
if (isLoading) {
return <section>Carregando...</section>;
}
if (isAuthenticated || loggedUser?.type == "Cliente") {
return children || <Outlet />;
} else {
return <Navigate to='/login' replace />;
}
}
const App_Router = () => {
return (
<AuthProvider>
<Routes>
<Route path='/' element={<Login />} />
<Route path='/login' element={<Login />} />
<Route path='/register' element={<UserForm />} />
<Route path='/home'>
<Route element={<ClientRoute />}>
<Route index element={<HomePage />} />
</Route>
<Route element={<ProRoute />}>
<Route path='Profissional'>
<Route index element={<HomePage />} />
<Route path='exercises' element={<Exercises />} />
</Route>
</Route>
</Route>
</Routes>
</AuthProvider>
);
}
export default App_Router;
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>
</>
);
}
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.
I'm using reach router for my routes. I was able to protect the dashboard and expose the login page and redirect if the user is not logged in but now if I enter a url it will do a quick redirecto to login and then to home instead of the page actually entered.
I noticed because of the useEffect to fetch the user the component renders twice: 1 without user (redirects to login) the other one with user and redirects to home.
Routes file
const AdminRoutes = () => {
return (
<Router>
<MainLayout path="/admin">
<HomePage path="/" />
<CarTransfer path="/cartransfers">
<CarTranserList path="/list" />
<CarTransferCreate path="/new" />
</CarTransfer>
<User path="/users">
<UserList path="/list" />
<UserCreate path="/new" />
</User>
</MainLayout>
<LoginPage path="/login" />
</Router>
);
};
Layout file
import { useState, useEffect } from "react";
import { Redirect, useNavigate } from "#reach/router";
import { Layout } from "antd";
import SiderMenu from "./SiderMenu";
import LayoutBanner from "./LayoutBanner";
import { useSelector, useDispatch } from "react-redux";
import {
userSelector,
fetchUserBytoken,
clearState,
} from "../../features/authSlice";
const { Content } = Layout;
const MainLayout = ({ children }) => {
const user = useSelector(userSelector);
const [collapsed, setCollapsed] = useState(false);
const navigate = useNavigate();
const dispatch = useDispatch();
const { isFetching, isError } = useSelector(userSelector);
useEffect(() => {
dispatch(
fetchUserBytoken({
token: localStorage.getItem("token"),
id: localStorage.getItem("id"),
})
);
}, []);
useEffect(() => {
if (isError) {
dispatch(clearState());
navigate("/login");
}
}, [isError]);
const handleOnCollapse = () => {
setCollapsed((prevState) => !prevState);
};
if (isFetching) {
return <div>Loading</div>;
}
if (user.id === "") {
return <Redirect noThrow to="/login" />;
}
return (
<Layout>
<SiderMenu collapsed={collapsed} handleOnCollapse={handleOnCollapse} />
<Layout>
<LayoutBanner
collapsed={collapsed}
handleOnCollapse={handleOnCollapse}
/>
<Content>{children}</Content>
</Layout>
</Layout>
);
};
export default MainLayout;
The second question would be how to get to the same page you were before the login redirect after login.
Thanks
I am using aws-amplify, react-hook in my project. The app have some private Routes has been define below:
const ProtectedRoute = ({render: C, props: childProps, ...rest}) => {
return (
<Route
{...rest}
render={rProps =>
(childProps) ? (
<C {...rProps} {...childProps} />
) : (
<Redirect
to={`/login?redirect=${rProps.location.pathname}${
rProps.location.search
}`}
/>
)
}
/>
);
}
In App.js, we change childProps to define whether user is login or not. But when childProps change, Switch not re rendering. What is the way to force React re rendering its Route because isAuthenticated is change but ProtectedRoute is not rerender.
const [isAuthenticated, userHasAuthenticated] = useState(null);
useEffect(() => {
onLoad();
}, []);
async function onLoad() {
try {
let user = await Auth.currentSession();
if (user.accessToken.payload) {
userHasAuthenticated(user.accessToken.payload);
}
} catch (e) {
if (e !== 'No current user') {
alert(e);
}
}
}
.....
const childProps = isAuthenticated;
return (
<ApolloProvider client={client} >
<div className="App">
<BrowserRouter>
<Route path='/'>
<div>
<Switch>
<Route path='/login' render={props => <Login {...props}/>} exact/>
<ProtectedRoute
exact
path='/admin/:name'
render={()=> <Admin />}
props={childProps}
/>
<Route path='/' render={props => <User {...props} />}/>
</Switch>
</div>
</Route>
</BrowserRouter>
</div>
</ApolloProvider>)
The route only renders again when you enter that URL again. You are doing a Redirect, meaning it will never have a chance to enter the same URL after authentication is complete. You should delay rendering the protected route until you have confirmed authentication:
useEffect(() => {
async function onLoad() {
try {
let user = await Auth.currentSession();
userHasAuthenticated(!!user.accessToken.payload);
} catch (e) {
if (e !== 'No current user') {
alert(e);
}
}
}
onLoad();
}, []);
...
const ProtectedRoute = ({render: C, props: childProps, ...rest}) => {
if (childProps === null) {
// app still waiting authentication
return 'Loading...';
}
return (
<Route
{...rest}
render={rProps =>
(childProps) ? (
<C {...rProps} {...childProps} />
) : (
<Redirect
to={`/login?redirect=${rProps.location.pathname}${
rProps.location.search
}`}
/>
)
}
/>
);
}