Reputation: 1045
I am trying to implement protected routes that are only accessible after the user logs in.
Basically, there are two routes: /login
Login component (public) and /
Dashboard component (protected). After the user clicks on the Login
button in /login
, an API is called which returns an accessToken
, which is then stored in localStorage. The protectedRoute
HOC checks if the token is present in the localStorage or not and redirects the user accordingly.
After clicking on the Login
button it just redirects back to the login page instead of taking user to Dashboard
. Not sure what is wrong with the logic.
asyncLocalStorage
is just a helper method for promise based localStorage operations.
App.js
import { useEffect, useState } from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Dashboard from "./Dashboard";
import Login from "./Login";
import ProtectedRoute from "./ProtectedRoute";
import { asyncLocalStorage } from "./asyncLocalStorage";
function App() {
const [auth, setAuth] = useState(false);
useEffect(() => {
const getToken = async () => {
const token = await asyncLocalStorage.getItem("accessToken");
if (token) setAuth(true);
};
getToken();
}, []);
return (
<div className="app">
<Router>
<Switch>
<ProtectedRoute exact path="/" isLoggedIn={auth} redirectTo="/login">
<Dashboard />
</ProtectedRoute>
<Route exact path="/login">
<Login />
</Route>
</Switch>
</Router>
</div>
);
}
export default App;
ProtectedRoute.js
import { Route, Redirect } from "react-router-dom";
const ProtectedRoute = ({ children, isLoggedIn, redirectTo, ...rest }) => {
return (
<Route
{...rest}
render={() => {
return isLoggedIn ? children : <Redirect to={redirectTo} />;
}}
/>
);
};
export default ProtectedRoute;
Dashboard.js
const Dashboard = () => {
return (
<div>
<h3>Dashboard</h3>
<button
onClick={() => {
localStorage.removeItem("accessToken");
window.location.href = "/login";
}}
>
Logout
</button>
</div>
);
};
export default Dashboard;
Login.js
import { asyncLocalStorage } from "./asyncLocalStorage";
const Login = () => {
return (
<div>
<h3>Login</h3>
<button
onClick={async () => {
// Making an API call here and storing the accessToken in localStorage.
await asyncLocalStorage.setItem(
"accessToken",
"SOME_TOKEN_FROM_API_RES"
);
window.location.href = "/";
}}
>
Login
</button>
</div>
);
};
export default Login;
asyncLocalStorage.js
export const asyncLocalStorage = {
setItem: (key, value) => {
Promise.resolve(localStorage.setItem(key, value));
},
getItem: (key) => {
return Promise.resolve(localStorage.getItem(key));
}
};
Upvotes: 0
Views: 527
Reputation: 42188
auth
State Never UpdatedYour ProtectedRoute
component relies on the value of auth
from the useState
in App
in order to determine whether the user is logged in or not. You set this value through a useEffect
hook which runs once when the App
is mounted. This hook is never run again and setAuth
is never called anywhere else. If the user is logged out when App
first mounts then the ProtectedRoute
will always receive isLoggedIn={false}
even after the user has logged in and the local storage has been set.
setAuth
from Login
A typical setup would check localStorage from the ProtectedRoute
, but I don't think that will work with the async
setup that you have here.
I propose that you pass both isLoggedIn
and setAuth
to the Login
component.
<Login isLoggedIn={!!auth} setAuth={setAuth}/>
In the Login
component, we redirect to home whenever we get a true
value of isLoggedIn
. If a users tries to go to "/login"
when they are already logged in, they'll get redirected back. When the log in by clicking the button, they'll get redirected when the value of auth
changes to true
. So we need to change that value.
The onClick
handler for the button
will await
setting the token in storage and then call setAuth(token)
. This will trigger the redirection.
import { asyncLocalStorage } from "./asyncLocalStorage";
import {Redirect} from "react-router-dom";
const Login = ({isLoggedIn, setAuth}) => {
if ( isLoggedIn ) {
return <Redirect to="/"/>
}
return (
<div>
<h3>Login</h3>
<button
onClick={async () => {
// Making an API call here and storing the accessToken in localStorage.
const token = "SOME_TOKEN_FROM_API_RES";
await asyncLocalStorage.setItem(
"accessToken",
token
);
setAuth(token);
}}
>
Login
</button>
</div>
);
};
export default Login;
Upvotes: 1