Was setting up react router to only display my Navbar on all routes except the login page. I created a Layout component that handles this and will return user to Login page if they are not authenticated. I currently store my state to local storage and if the value is true, the user is still authenticated and can remain on their current page. This works great although when the page is refreshed, it returns the user back to the login page even though local storage item is set to true. Any advice on what I may be doing wrong or another workaround for what I'm trying to accomplish? Thank you.
App.js
export const AuthContext = createContext();
function App() {
const stillAuth = window.localStorage.getItem("auth state");
console.log("still auth: " stillAuth);
const [isAuthenticated, setIsAuthenticated] = useState(stillAuth);
console.log("isAuth: " isAuthenticated);
return (
<div className="App d-flex flex-column" style={{ minHeight: "100vh" }}>
<AuthContext.Provider value={{ isAuthenticated, setIsAuthenticated }}>
<Routes>
<Route path="login" element={<Login />} />
<Route path="/" element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/DevExamples" element={<DeveloperExamples />} />
<Route path="/CliExamples" element={<ClientExamples />} />
<Route path="*" element={<Error />} />
</Route>
</Routes>
</AuthContext.Provider>
<Footer />
</div>
);
}
function Layout() {
const { isAuthenticated } = useContext(AuthContext);
if (isAuthenticated != true) return <Navigate to="/login" />;
return (
<>
<NavBar />
</>
);
}
export default App;
Login.jsx
const Login = () => {
const [username, setUserName] = useState("");
const [password, setPassword] = useState("");
const navigate = useNavigate();
const { isAuthenticated, setIsAuthenticated } = useContext(AuthContext);
const handleLogin = () => setIsAuthenticated(true);
const handleLogout = () => setIsAuthenticated(false);
const updateToken = (username, password) => {
// make axios call and check if username & password are valid
window.localStorage.setItem("auth state", true);
handleLogin();
navigate("/");
// excluded for now to test and develop
// LoginService.authLogin({
// username: username,
// password: password,
// }).then(() => {
// });
};
return (
<div>
<h1>Please Log In</h1>
{/* <form onSubmit={handleSubmit}> */}
<label>
<p>Username</p>
<input
type="text"
onChange={(e) => setUserName(e.target.value)}
value={username}
required
/>
</label>
<br />
<label>
<p>Password</p>
<input
type="password"
onChange={(e) => setPassword(e.target.value)}
value={password}
required
/>
</label>
<div>
<button
// component={Link}
// to="/home"
onClick={() => updateToken(username, password)}
>
<Link to="/">Sign In</Link>
</button>
</div>
{/* </form> */}
</div>
);
};
CodePudding user response:
Issue
The issue here is that values stored into localStorage are stringified.
window.localStorage.setItem("state", true);
const state = window.localStorage.getItem("state"); // "true"
Once the boolean "auth state" value is saved into localStorage it becomes a string literal, the Layout
is comparing a string literal against a boolean.
console.log("true" != true); // true
console.log("true" !== true); // true
console.log("false" != true); // true
console.log("false" !== true); // true
The expression isAuthenticated != true
will always evaluate true and the redirect to "/login"
will be effected.
Solution
You should generally be in the habit of explicitly JSON stringifying and parsing the data you persist to localStorage.
window.localStorage.setItem("state", JSON.stringify(true));
const state = JSON.parse(window.localStorage.getItem("state")); // true
function App() {
const stillAuth = JSON.parse(window.localStorage.getItem("auth state")); // <-- parse
const [isAuthenticated, setIsAuthenticated] = useState(stillAuth);
console.log({ stillAuth, isAuthenticated });
return (
<div className="App d-flex flex-column" style={{ minHeight: "100vh" }}>
<AuthContext.Provider value={{ isAuthenticated, setIsAuthenticated }}>
<Routes>
<Route path="login" element={<Login />} />
<Route path="/" element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/DevExamples" element={<DeveloperExamples />} />
<Route path="/CliExamples" element={<ClientExamples />} />
<Route path="*" element={<Error />} />
</Route>
</Routes>
</AuthContext.Provider>
<Footer />
</div>
);
}
import { Outlet } from 'react-router-dom';
function Layout() {
const { isAuthenticated } = useContext(AuthContext);
if (!isAuthenticated) return <Navigate to="/login" replace />;
return (
<>
<NavBar />
<Outlet /> // <-- so nested routes can render content!!
</>
);
}
Login
const updateToken = (username, password) => {
// make axios call and check if username & password are valid
window.localStorage.setItem("auth state", JSON.stringify(true)); // <-- stringify
handleLogin();
navigate("/");
// excluded for now to test and develop
// LoginService.authLogin({
// username: username,
// password: password,
// }).then(() => {
// });
};