Home > OS >  React Router Maintain state during page refresh
React Router Maintain state during page refresh

Time:01-25

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>
  );
};

Sandbox

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(() => {
  // });
};
  • Related