This is the PrivateRoute component:
import React from "react";
import { Navigate, Outlet, useLocation } from "react-router-dom";
import { useAuth } from "../../Firebase/utils";
const PrivateRoute = ({ children }) => {
let currentUser = useAuth();
if (!currentUser) {
return <Navigate to="/login" />;
}
return children;
};
export default PrivateRoute;
App.js
<Route
path="/Page"
element={
<PrivateRoute>
<Page />
</PrivateRoute>
}
/>
Is there a way where I could put a loading in the PrivateRoute?
This is my useAuth:
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 };
}
This is the App.js
function App() {
const { currentUser, isLoading } = useAuth();
const user = auth.currentUser;
const navigate = useNavigate();
console.log(currentUser?.email);
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("/Home");
// ...
} else {
// User is signed out
// ...
navigate("/login");
}
});
return () => {
unsub();
};
}, []);
return (
<div>
<div>
<Routes>
{!isLoading ? (
<>
<Route
path="/login"
element={
<SignInPage />
}
/>
<Route
path="/Home"
element={
<PrivateRoute>
<Homepage />
</PrivateRoute>
}
/>
</>
) : (
<>
<Route
path="/login"
element={
<SignInPage />
}
/>
</>
)}
<Route
path="/reset-password"
element={
<ResetPasswordPage />
}
/>
</Routes>
</div>
</div>
);
}
export default App;
The useAuth hook returns an object with an isLoading state that is initially true:
export function useAuth() {
const [isLoading, setIsLoading] = useState(true);
const [currentUser, setCurrentUser] = useState();
useEffect(() => {
const unsub = onAuthStateChanged(auth, (user) => {
setCurrentUser(user);
setIsLoading(false); // finished checking
});
return unsub;
}, []);
return { currentUser, isLoading };
}
Access this isLoading state in the PrivateRoute component to conditionally render null or some loading indicator, anything other than the children prop or the redirect to the login route.
const PrivateRoute = ({ children }) => {
const { currentUser, isLoading } = useAuth();
if (isLoading) {
return null; // or loading spinner, etc...
}
if (!currentUser) {
return <Navigate to="/login" replace />;
}
return children;
};
You could also just render all the routes, some with the PrivateRoute wrapper where necessary.
function App() {
const { currentUser } = useAuth();
const navigate = useNavigate();
console.log(currentUser?.email);
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("/Home");
// ...
} else {
// User is signed out
// ...
navigate("/login");
}
});
return () => {
unsub();
};
}, []);
return (
<div>
<div>
<Routes>
<Route path="/login" element={<SignInPage />} />
<Route
path="/Home"
element={
<PrivateRoute>
<Homepage />
</PrivateRoute>
}
/>
<Route path="/reset-password" element={<ResetPasswordPage />} />
</Routes>
</div>
</div>
);
}
Related
function App() {
const [token, setToken] = useState();
useEffect(() => {
const auth = localStorage.getItem('auth_token');
setToken(auth);
}, [token]);
return (
<div>
<FullNavBar />
<Routes>
<Route path='/login' element={<Login />}></Route>
<Route path='/register' element={<Register />}></Route>
<Route path='/forgot_password' element={<ForgotPassword />}></Route>
<Route element={<ProtectedRoutes />}>
<Route path='/home' element={<Home />}></Route>
<Route path='/active_lottery' element={<Activelottery />}></Route>
</Route>
</Routes>
</div>
);
}
export default App;
Protected Routes:
import { Navigate, Outlet } from 'react-router';
const authorization = () => {
const token = localStorage.getItem('auth_token');
return token ? true : false;
};
const ProtectedRoutes = () => {
const isAuth = authorization();
return isAuth ? <Outlet /> : <Navigate to='/login' />;
};
export default ProtectedRoutes;
How can I make that navbar to render after changing the page?
After I'm trying to log in I'm redirected to the homepage but I still have the previous NavBar,is re-rendering after I'm refreshing the page.
Login redirect:
const onSubmitHandler = async (event) => {
event.preventDefault();
await axios
.post(
'url/dev/user/login',
loginForm,
)
.then((response) => {
localStorage.setItem(
'auth_token',
response.data.AuthenticationResult.AccessToken,
);
toast.success('Your in now');
setTimeout(() => {
navigate('/home');
}, 1000);
})
.catch((err) => {
console.log(err);
toast.error(err.response.data.message);
});
};
and in the fullNavBar component I want to update the pages from navBar.
export default function FullNavBar() {
const [menuOpen, setMenuOpen] = useState(false);
const [auth, setAuth] = useState(false);
const token = localStorage.getItem('auth_token');
useEffect(() => {
if (token) {
console.log(token);
setAuth(true);
}
}, [auth]);
return (
<div className="bg-gradient-to-r from-yellow-400 to-yellow-500">
<Navbar token={auth} menuOpen={menuOpen} setMenuOpen={setMenuOpen} />
{menuOpen && <MobileMenu>{navLinks(auth)}</MobileMenu>}
</div>
);
}
const navLinks = token => {
let pages = ['About', 'Contact', 'Login'];
if (token) {
pages = ['Buy', 'Tickets', 'History', 'Profile'];
}
return pages.map(page =>
page === 'Profile' ? (
<button key={page} className="inline-block">
{' '}
<img
className="rounded-full w-11 h-11"
src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQhumf_G7azRo-qCcnB533PPwZx386EK1cozzMAMEtW3A&s"
></img>
</button>
) : page === 'Tickets' ? (
<a
key={page}
className="no-underline text-gray-800 font-semibold hover:text-gray-500 relative bottom-4"
href={`${page.toLowerCase()}`}
>
100 {page}
</a>
) : (
<a
key={page}
className="no-underline text-gray-800 font-semibold hover:text-gray-500 relative bottom-4"
href={`${page.toLowerCase()}`}
>
{page}
</a>
),
);
};
everything is working fine after I'm refreshing the page but when it's redirected from login to home the navBar component isn't called
The issue here is that the code is trying to use localStorage actively as the source of truth. It is only to be used for longer-term storage of your React state. Use the token state in App as the active source of truth and pass the token state and state updater function down as props to the relevant components that care.
Example:
App
Use a state initializer function to set/provide the initial token state from localStorage. Use the useEffect hook to persist token state changes to localStorage.
function App() {
const [token, setToken] = useState(() => {
// Initialize from localStorage
return JSON.parse(localStorage.getItem('auth_token'));
});
useEffect(() => {
// Persist state changes to localStorage
localStorage.setItem('auth_token', JSON.stringify(token));
}, [token]);
return (
<div>
<FullNavBar token={token} /> // <-- pass token as prop
<Routes>
<Route
path='/login'
element={<Login setToken={setToken} />} // <-- pass state updater function
/>
<Route path='/register' element={<Register />} />
<Route path='/forgot_password' element={<ForgotPassword />} />
<Route
element={<ProtectedRoutes isAuth={!!token} />} // <-- pass isAuth prop
>
<Route path='/home' element={<Home />} />
<Route path='/active_lottery' element={<Activelottery />} />
</Route>
</Routes>
</div>
);
}
ProtectedRoutes
import { Navigate, Outlet } from 'react-router';
const ProtectedRoutes = ({ isAuth }) => {
return isAuth ? <Outlet /> : <Navigate to='/login' replace />;
};
export default ProtectedRoutes;
Login
Destructure and access the passed setToken callback and call it and pass the new token value to update the state in App.
const Login = ({ setToken }) = { // <-- access state updater function
...
const onSubmitHandler = async (event) => {
event.preventDefault();
await axios
.post('url/dev/user/login', loginForm)
.then((response) => {
setToken(response.data.AuthenticationResult.AccessToken); // <-- save token to state
toast.success('You're in now');
setTimeout(() => {
navigate('/home');
}, 1000);
})
.catch((err) => {
console.log(err);
toast.error(err.response.data.message);
});
};
...
};
FullNavBar
Access the passed token prop. Instead of using the token to set some local auth state just consume the token directly.
export default function FullNavBar({ token }) { // <-- access token prop
const [menuOpen, setMenuOpen] = useState(false);
return (
<div className="bg-gradient-to-r from-yellow-400 to-yellow-500">
<Navbar
token={token} // <-- pass to Navbar
menuOpen={menuOpen}
setMenuOpen={setMenuOpen}
/>
{menuOpen && (
<MobileMenu>
{navLinks(token)} // <-- pass to navLinks utility
</MobileMenu>
)}
</div>
);
}
const navLinks = token => {
let pages = ['About', 'Contact', 'Login'];
if (token) {
pages = ['Buy', 'Tickets', 'History', 'Profile'];
}
return pages.map(page =>
....,
);
};
What I am trying to do
I'm using Firebase v9 and React-router-dom v5.3.0 to make a sign up form which creates a new account and redirects the user to the home screen when an account is created. The route for the home screen is "/".
The problem
My plan is to call history.push("/") after calling the sign up function, which should take me to the home screen. However, when running the code, history.push("/") only updated the URL and did not redirect me to the home screen. I had to reload the page for the home screen to show up, otherwise I'd just be stuck in the sign up form. I have been fiddling with the code and what surprises me is when I remove await signup(email, password), history.push works just as intended. I suspect this behavior has something to do with firebase's sign up function, but I don't know what it is. Can someone offer an explanation?
The code
Here's my code for the sign up form:
import { useState } from "react";
import { useHistory } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext";
function Signup() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [cfPassword, setCfPassword] = useState("");
const {signup} = useAuth();
const history = useHistory();
async function handleSubmit(e) {
e.preventDefault();
if (password !== cfPassword) {
console.log("Passwords do not match!");
return;
}
try {
await signup(email, password);
history.push("/"); // Problematic code is here. This works fine when I remove the previous line.
} catch (error) {
console.log(error.message);
}
}
return (
<div>
<h1>Create an account</h1>
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email"/>
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password"/>
<input type="password" value={cfPassword} onChange={(e) => setCfPassword(e.target.value)} placeholder="Confirm Password"/>
<input type="submit" value="Sign up"/>
</form>
</div>
);
}
export default Signup;
The code containing the authentication context and the related function for signing up:
import { auth } from "../firebase";
import { useState, useEffect, useContext, createContext } from "react";
import { createUserWithEmailAndPassword } from "firebase/auth";
const AuthContext = createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({children}) {
const [currentUser, setCurrentUser] = useState();
const [isLoading, setIsLoading] = useState(true);
function signup(email, password) {
return createUserWithEmailAndPassword(auth, email, password);
}
const value = {
currentUser,
signup
}
useEffect(() => {
const unsubscriber = onAuthStateChanged(auth, (user) => {
setIsLoading(true);
setCurrentUser(user);
setIsLoading(false);
});
return unsubscriber;
}, []);
return (
<AuthContext.Provider value={value}>
{!isLoading && children}
</AuthContext.Provider>
);
}
My App.jsx component containing the router.
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { AuthProvider } from './contexts/AuthContext';
import Home from './pages/mains/home/Home';
import Signup from './components/Signup';
function App() {
return (
<AuthProvider>
<Router>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/signup" component={Signup} />
</Switch>
</Router>
</AuthProvider>
)
}
export default App;
So, after testing and revising my code, I found out that the problem lies in setIsLoading(true) in AuthProvider's useEffect. Remove that line and everything works like a charm. Hopefully this helps someone experiencing the same problem. :)
Issue
Yes, I see now where an issue lies with using the isLoading state to conditionally render the entire app. After creating a new user it seems the onAuthStateChanged handler is invoked and current user is reloaded. When toggling the isLoading state true the AuthContext's children are unmounted.
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [isLoading, setIsLoading] = useState(true);
function signup(email, password) {
return createUserWithEmailAndPassword(auth, email, password);
}
const value = {
currentUser,
signup
}
useEffect(() => {
const unsubscriber = onAuthStateChanged(auth, (user) => {
setIsLoading(true); // <-- setting true
setCurrentUser(user);
setIsLoading(false);
});
return unsubscriber;
}, []);
return (
<AuthContext.Provider value={value}>
{!isLoading && children} // <-- unmounts children
</AuthContext.Provider>
);
}
Solution
A more practical solution is to add the isLoading state to the context value and create a protected route component that handles conditionally rendering null or a loading indicator, or the routed component or a redirect to log in.
Example:
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [isLoading, setIsLoading] = useState(true);
function signup(email, password) {
return createUserWithEmailAndPassword(auth, email, password);
}
useEffect(() => {
const unsubscriber = onAuthStateChanged(auth, (user) => {
setIsLoading(true);
setCurrentUser(user);
setIsLoading(false);
});
return unsubscriber;
}, []);
const value = {
currentUser,
isLoading,
signup
}
return (
<AuthContext.Provider value={value}>
{children} // <-- keep children mounted!
</AuthContext.Provider>
);
}
...
const ProtectedRoute = props => {
const location = useLocation();
const { currentUser, isLoading } = useAuth();
if (isLoading) {
return null;
}
return currentUser
? <Route {...props} />
: (
<Redirect
to={{
pathname: "/login",
state: { from: location } // <-- used to redirect back after auth
}}
/>
);
};
...
function App() {
return (
<AuthProvider>
<Router>
<Switch>
<ProtectedRoute path="/somethingToProtect" component={<Private />} />
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
<Route path="/" component={Home} />
</Switch>
</Router>
</AuthProvider>
)
}
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;
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.
You could just use AuthContext to pass parent to child component like below:
import React from 'react'
import { createContext, useState } from "react";
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({});
return (
<AuthContext.Provider value={{ auth, setAuth }}>
{children}
</AuthContext.Provider>
)
}
export default AuthContext;
this is my router,
the for some reason the navbar don't update when the prop updates
but all the other components do update with the same prop
I use useState for the user but the navbar still wont change
export default function CRouter() {
const [token, setToken] = useLocalStorage('auth', '')
const [user, setUser] = useState(false);
const GetUser = () => {
if (token !== "" && !user) {
axios.post('/auth/getUser', { token }).then(res => {
if (res.status === 200) {
setUser(res.data);
} else {
setToken('');
setUser(false);
//mandarlo al login
}
})
}
}
GetUser()
<Router>
<NavBar user={user}></NavBar>
<Switch>
<Route
exact
path='/'
render={(props)=>(
<Home></Home>
)}
></Route>
<Route
exact
path="/forum"
render={(props) => (
<Forum user={user}></Forum>
)}
></Route>
</Switch>
</Router>
)}
I have protected routes that passing auth enabled true or false to allow users access auth page. Every page load, it calls the auth token API and check for valid. If token invalid, redirect to login page.
ProtectedRoute.js
const ProtectedRoute = ({isEnabled, ...props}) => {
return (isEnabled) ? <Route {...props} /> : <Redirect to="/login"/>;
};
export default ProtectedRoute;
Routes.js
import {withRouter, Switch, Route } from "react-router-dom";
export default withRouter(({ location }) => {
const [isAuth, setIsAuth] = useState(false)
useLayoutEffect(() => {
(async() => {
if(accessToken){
let res = await ValidateLoginToken(accessToken)
if (res && res.data && res.data.status === 200){
setIsAuth(res.data.valid)
} else setIsAuth(false);
} else setIsAuth(false)
})()
},[isAuth])
return (
<Switch>
<ProtectedRoute path="/dashboard" component={Dashboard} isEnabled={isAuth} />
<Route path="/" component={Login} />
</Switch>
)
}
App.js
const history = createBrowserHistory();
function App() {
return (
<Router history={history}>
<Routes/>
</Router>
)
}
export default App;
Dashboard.js
export const Dashboard = () => {
return (
<div class="ui form-user center raised padded segment">
<a href="/section1">
<div id="section1" class="ui floated right basic red button">
Directory #1
</div>
</a>
</div>
)
}
Problem
When auth users browse authenticated page (Dashboard), redirects to login page. The reason is that verify token API returns after ProtectedRoute rendered as isAuth equals to false.
You can use another state variable to wait for api execution before initializing routes.
const [isAuth, setIsAuth] = useState(false)
const [checked, setChecked] = useState(false)
useLayoutEffect(() => {
(async() => {
if(accessToken){
let res = await ValidateLoginToken(accessToken)
if (res && res.data && res.data.status === 200){
setIsAuth(res.data.valid)
} else setIsAuth(false);
} else setIsAuth(false)
setChecked(true)
})()
},[isAuth])
Then in Routes, you can do something like:
<Switch>
{
!checked?(<React.Fragment/>):!isAuth?(<Route path="/" component={Login} />):(<ProtectedRoute path="/dashboard" component={Dashboard} loginState={loginState} />)
}
</Switch>
I usually separate session routes and no session routes in separate hooks and it works fine.
Instead of isAuth being a boolean, you use a string or number to increase the number of states. For example rename isAuth to loginState which can be "pending", "authenticated" or "unauthenticated". Then use "pending" as the initial state and add an additional scenario. You could for example return null to render nothing, render a spinning circle, etc.
Here is an example rendering null (nothing) while the login token is being authenticated:
ProtectedRoute.js
const loginRoutes = {
pending: ( ) => null,
authenticated: (...props) => <Route {...props} />,
unauthenticated: ( ) => <Redirect to="/login"/>,
};
const ProtectedRoute = ({loginState = "pending", ...props}) => {
const LoginRoute = loginRoutes[loginState];
return <LoginRoute {...props} />;
};
export default ProtectedRoute;
Route.js
import { withRouter, Switch, Route } from "react-router-dom";
export default withRouter(({ location }) => {
const [loginState, setLoginState] = useState("pending")
useLayoutEffect(() => {
(async() => {
if(accessToken){
let res = await ValidateLoginToken(accessToken)
if (res && res.data && res.data.status === 200) {
setLoginState(res.data.valid ? "authenticate" : "unauthenticated")
} else setLoginState("unauthenticated");
} else setLoginState("unauthenticated")
})()
}, [loginState])
return (
<Switch>
<ProtectedRoute path="/dashboard" component={Dashboard} loginState={loginState} />
<Route path="/" component={Login} />
</Switch>
)
}
As a disclaimer, I have no experience with React Router so I've kept the example as close to the original as possible.
You could use the initial state as null to differentiate the different states of your component.
null -> API token no called yet.
true -> token verified
false -> token verification failed.
export default function ProtectedRoute(props) {
const [isAuth, setIsAuth] = React.useState(false)
const history = useHistory();
React.useEffect(() => {
async function validateToken() {
if(accessToken){
let res = await ValidateLoginToken(accessToken)
if (res && res.data && res.data.status === 200){
setIsAuth(true)
return;
}
}
history.push('/login')
}
validateToken();
}, [isAuth]);
if (isAuth === null) return null;
return props.children;
}
export default function App() {
return (
<Router>
<Switch>
<Route path="/" exact component={HomePage} />
<Route exact path="/login" component={Login} />
<ProtectedRoute>
<Route path="/dashboard" component={Dashboard} />
</ProtectedRoute>
</Switch>
</Router>
)
}