I just started using React. I’m building a CRUD app with it. I made some pages that can be browsed using react-router-dom
with authentication. If the user is logged in, they are able to see those pages. Otherwise, we will be redirected to the sign-in page.
Everything works fine except for refreshing pages. When I’m at any page and click "refresh" in the browser, it should reload the last page we are currently at. But instead, the app re-renders and eventually goes back to the homepage. This is not what was intended.
import React, { useEffect, useState } from "react";
import "./app.css";
import Sidebar from "./components/sidebar/Sidebar";
import Topbar from "./components/topbar/Topbar";
import Home from "./pages/Home/Home";
import {
BrowserRouter as Router,
Route,
Routes,
Navigate,
} from "react-router-dom";
import Users from "./pages/Users/Users";
import Patients from "./pages/Patients/Patients";
import PatientDetails from "./pages/PatientDetails/PatientDetails";
import AccountSettings from "./pages/AccountSettings/AccountSettings";
import SignIn from "./pages/SignIn/SignIn";
import UpdateUserForm from "./pages/UserForm/UpdateUserForm";
import CreateUserForm from "./pages/UserForm/CreateUserForm";
import CreatePatientForm from "./pages/PatientForm/CreatePatientForm";
import UpdatePatientForm from "./pages/PatientForm/UpdatePatientForm";
import CreateAppointmentForm from "./pages/AppointmentForm/CreateAppointmentForm";
import UpdateAppointmentForm from "./pages/AppointmentForm/UpdateAppointmentForm";
import axios from "axios";
import UserContext from "./utils/UserContext";
// use react-router before v.6.5.0
export default function App() {
const [user, setUser] = useState(false);
const getLoginStatus = async () => {
const data = localStorage.getItem("DENTAL_ART_LOGGED_USER");
console.log(data);
if (data) {
const res = await axios.get("http://localhost:3001/api/login/status", {
params: JSON.parse(data),
});
if (res.data) {
setUser(res.data);
}
}
};
const postUserLogin = async (userValues) => {
try {
const res = await axios.post(
"http://localhost:3001/api/signin",
userValues
);
setUser(res.data.user);
// save user to localStorage
localStorage.setItem("DENTAL_ART_LOGGED_USER", JSON.stringify(res.data));
} catch (error) {
console.log(error);
}
};
const getUserLogout = async (e) => {
// grab user session information on localStorage, passed it to server as an information to delete that session in DB.
const data = localStorage.getItem("DENTAL_ART_LOGGED_USER");
try {
const res = await axios.get("http://localhost:3001/api/signout", {
params: JSON.parse(data),
});
// reset the user state (value: false)
setUser(res.data);
localStorage.removeItem("DENTAL_ART_LOGGED_USER");
getLoginStatus();
} catch (error) {
console.log(error);
}
};
useEffect(() => {
if (!user) {
getLoginStatus();
}
}, []);
return (
<UserContext.Provider value={{ user, postUserLogin, getUserLogout }}>
<Router>
<Routes>
<Route
path="/signin"
element={user ? <Navigate to="/" /> : <SignIn />}
/>
<Route
path="*"
element={
<>
<Topbar />
<div className="container">
<Sidebar />
<Routes>
<Route
index
exact
path="/"
element={user ? <Home /> : <Navigate to="/signin" />}
/>
<Route
exact
path="/patients"
element={user ? <Patients /> : <Navigate to="/signin" />}
/>
<Route
exact
path="/users"
element={user ? <Users /> : <Navigate to="/signin" />}
/>
<Route
exact
path="/patient/:patientId"
element={
user ? <PatientDetails /> : <Navigate to="/signin" />
}
/>
;
<Route
exact
path="/patient/:patientId?/appointmentForm/create"
element={
user ? (
<CreateAppointmentForm />
) : (
<Navigate to="/signin" />
)
}
/>
<Route
exact
path="/patient/:patientId?/appointmentForm/:appointmentId?/edit"
element={
user ? (
<UpdateAppointmentForm />
) : (
<Navigate to="/signin" />
)
}
/>
<Route
exact
path="/accountSettings"
element={
user ? <AccountSettings /> : <Navigate to="/signin" />
}
/>
<Route
exact
path="/userForm/:userId?/edit"
element={
user ? <UpdateUserForm /> : <Navigate to="/signin" />
}
/>
<Route
exact
path="/userForm/create"
element={
user ? <CreateUserForm /> : <Navigate to="/signin" />
}
/>
<Route
exact
path="/patientForm/create"
element={
user ? <CreatePatientForm /> : <Navigate to="/signin" />
}
/>
<Route
exact
path="/patientForm/:patientId?/edit"
element={
user ? <UpdatePatientForm /> : <Navigate to="/signin" />
}
/>
</Routes>
</div>
</>
}
/>
</Routes>
</Router>
</UserContext.Provider>
);
}
Some resources suggested using localStorage
to save the current route/path using the useLocation
hook, so whenever the path we are currently at changes, the browser will save that path to localStorage
, and it will get it back when refreshes happen.
But I’m not sure that is the best practice, since I don’t find useLocation
can be implemented inside the React App
component.
2
Answers
You should handle authenticate user in pages Home, Patient using useEffect inside that page (or better use a HOC to wrap the pages).
Because whenever you refresh, user state will be reset to false to you’ll be navigate to /signin route, then when successfully authenticated, you’ll be redirect to home
Issue
Since the callback of an
useEffect
runs after the JSX is rendered, on refresh, theuser
state isfalse
, that default value given touseState
. So the page you are currently on redirects to"/signin"
. And meanwhile,getLoginStatus
finishes running, anduser
is set totrue
, so you are redirected back to"/"
.Solution
The easiest way to solve this issue, and that adds little changes to your current code, is to set up a loading state so you render the JSX only after
getLoginStatus
finish running. Like so: