从本地存储中的登录状态重定向

huangapple go评论86阅读模式
英文:

redirecting by loginstatus from local.storage

问题

我尝试实现会话管理和私有路由到我的应用程序中,但出现了一些问题。我现在使用的解决方案似乎有bug。我的登录会将登录状态更新为true,并将其存储在localstorage中作为storedLoginStatus。然后我尝试检查此值,以便在刷新页面时保持我原来所在的页面,即使在刷新或更改语言后也是如此,这将刷新页面,但每次刷新都会将我发送到未登录的屏幕。

有人知道我该如何解决这个问题吗?

这是处理登录状态和设置它的Navbar上下文(它还处理项目选择,与此问题无关):
[NavbarContext代码]

index.js处理路由:
[index.js代码]

这是登录屏幕及其逻辑:
[LoginScreen代码]

我知道这有点混乱,但我需要尽快使其正常工作,之后我会整理它,我认为问题可能出现在PrivateRoutes中我正在重定向的outlet对象上,因为我在控制台记录了storedLoginStatus的值,它似乎是true,应该重定向到outlet,这应该是当前页面,而不是“未登录”的页面。

有什么建议吗?也许我应该实现会话状态并解决整个问题?

非常感谢你们的帮助。

英文:

im trying to implement session management and private routes into my application and for some reason the solution im using now is bugging in a way. my log in updates login status to true if logged in,and stores it in localstorage as storedLoginStatus then im trying to check this value when refreshing the page so it keeps me on the same page i was even after refresh or language change which refreshes the page but every refresh sends me to the not logged in screen.

anyone know how i can fix this?

here is PrivateRoutes.js which handles redirecting upon loginstatus:

import { Outlet, Navigate, useLocation } from "react-router-dom";
import { useContext, useEffect } from "react";
import { NavbarContext } from "../../NavbarContext";

const PrivateRoutes = () => {
  const { loginStatus, setLoginStatus } = useContext(NavbarContext);
   const storedLoginStatus = JSON.parse(localStorage.getItem("loginStatus"));
   let location = useLocation();

  useEffect(() => {
    
    const storedLoginStatus = JSON.parse(localStorage.getItem("loginStatus"));
    if (storedLoginStatus !== null) {
      setLoginStatus(storedLoginStatus);
      console.log("stored = " + storedLoginStatus);
    }
  }, [setLoginStatus]);

  return storedLoginStatus ? <Outlet /> : <Navigate to="/*" />;
};

export default PrivateRoutes;



this is navbar context which handles the loginstatus and sets it (it also handles project seletion, not relevant to this question):

import React, { createContext, useState, useEffect } from "react";
import NavbarData from "./components/Navbar/NavbarData";

export const NavbarContext = createContext();

export const NavbarProvider = ({ children }) => {
  const [loginStatus, setLoginStatus] = useState(false);
  const [navbarData, setNavbarData] = useState([]);
  const [selectedProjectIndex, setSelectedProjectIndex] = useState(0);

  useEffect(() => {
    const storedProjectIndex = localStorage.getItem("selectedProjectIndex");
    const storedLoginStatus = localStorage.getItem("loginStatus");

    if (storedProjectIndex !== null) {
      const parsedIndex = parseInt(storedProjectIndex, 10);
      if (
        !isNaN(parsedIndex) &&
        parsedIndex >= 0 &&
        parsedIndex < NavbarData.length
      ) {
        setSelectedProjectIndex(parsedIndex);
      }
    }

    if (storedLoginStatus !== null) {
      setLoginStatus(JSON.parse(storedLoginStatus));
    }
  }, []);

  useEffect(() => {
    localStorage.setItem(
      "selectedProjectIndex",
      selectedProjectIndex.toString()
    );
    localStorage.setItem("loginStatus", JSON.stringify(loginStatus));

    setNavbarData(NavbarData[selectedProjectIndex]?.tabs || []);
  }, [selectedProjectIndex, loginStatus]);

  return (
    <NavbarContext.Provider
      value={{
        navbarData,
        selectedProjectIndex,
        setSelectedProjectIndex,
        setLoginStatus,
        loginStatus,
      }}
    >
      {children}
    </NavbarContext.Provider>
  );
};



index.js handles routing:


import { createRoot } from "react-dom/client";
import { RouterProvider, createBrowserRouter } from "react-router-dom";

import "./i18n";

import React from "react";

import MonitoringOverview from "./routes/MonitoringOverview";
import PerformanceQuality from "./routes/PerformanceQuality";
import Finance from "./routes/Finance";
import Navbar from "./components/Navbar/Navbar";
import "./App.css";
import LoginScreen from "./routes/LoginScreen/LoginScreen";
import UserConfig from "./routes/UserConfig/UserConfig.js";
import Simulation from "./routes/Simulation";
import Fraud from "./routes/Fraud";
import Deployment from "./routes/Deployment";
import Planning from "./routes/Planning";
import Settings from "./routes/Settings";

import PrivateRoutes from "./routes/Utils/PrivateRoutes";
import NotLogged from "./routes/Utils/NotLoggedIn";

const router = createBrowserRouter([
  {
    path: "/",
    element: (
      <NavbarProvider>
        <LoginScreen />
      </NavbarProvider>
    ),
  },
  {
    path: "/*",
    element: (
      <NavbarProvider>
        <NotLogged />
      </NavbarProvider>
    ),
  },

  {
    element: (
      <>
        <NavbarProvider>
          <Navbar />
          <PrivateRoutes />
        </NavbarProvider>
      </>
    ),
    children: [
      {
        path: "monitoringoverview",
        element: <MonitoringOverview />,
      },

      {
        path: "performancequality",
        element: <PerformanceQuality />,
      },

      {
        path: "finance",
        element: <Finance />,
      },
      {
        path: "simulation",
        element: <Simulation />,
      },
      {
        path: "userconfig",
        element: <UserConfig />,
      },
      {
        path: "fraud",
        element: <Fraud />,
      },
      {
        path: "deployment",
        element: <Deployment />,
      },
      {
        path: "planning",
        element: <Planning />,
      },
      {
        path: "settings",
        element: <Settings />,
      },

      // 
      {
        path: "cashflowoverview",
        element: <CashflowOverview />,
      },

      //Elections
      {
        path: "elections",
        element: <Elections />,
      },
      {
        path: "whatif",
        element: <Whatif />,
      },

      //
      {
        path: "overview",
        element: <Overview />,
      },
      {
        path: "resourcemanagement",
        element: <ResourceManagement />,
      },

      //SuperGaz
      {
        path: "energymonitoring",
        element: <EnergyMonitoring />,
      },

      //
      {
        path: "monitoring",
        element: <Monitoring />,
      },
      {
        path: "inquiry",
        element: <Inquiry />,
      },

      //

     
    ],
  },
]);

createRoot(document.getElementById("root")).render(
  <RouterProvider router={router} />
);




and this is the login screen with its logic:

import React, { useState, useContext } from "react";
import "./LoginScreen.css";
import { Navigate } from "react-router-dom";
import { NavbarContext } from "../../NavbarContext";

// i18n implementation
import { useTranslation } from "react-i18next";
import i18next from "i18next";

function LoginScreen() {
  //Context Provider

  // React States

  const [errorMessages, setErrorMessages] = useState({});
  const [isSubmitted, setIsSubmitted] = useState(false);
  const { setLoginStatus } = useContext(NavbarContext);

  const { t, i18n } = useTranslation();
  const lngs = {
    en: { nativeName: "English" },
    fr: { nativeName: "French" },
  };

  const lng = i18next.language;

  const changeLanguage = (lng) => {
    i18n.changeLanguage(lng);

    window.location.reload();
  };

  //Database for demo authentication
  const database = [
    {
      username: "11111",
      password: "1111111",
    },
    {
      username: "1111",
      password: "1111111",
    },
  ];

  const errors = {
    uname: "invalid username",
    pass: "invalid password",
  };

  //Submit handling
  const handleSubmit = (event) => {
    //Prevent page reload
    event.preventDefault();

    var { uname, pass } = document.forms[0];

    // Find user login info
    const userData = database.find((user) => user.username === uname.value);

    // Compare user info
    if (userData) {
      if (userData.password !== pass.value) {
        // Invalid password
        setErrorMessages({ name: "pass", message: errors.pass });
      } else {
        setIsSubmitted(true);
        setLoginStatus(true);
      }
    } else {
      // Username not found
      setErrorMessages({ name: "uname", message: errors.uname });
    }
  };

  //Error messages handling
  const renderErrorMessage = (name) =>
    name === errorMessages.name && (
      <div className="error">{errorMessages.message}</div>
    );

  //Login Form
  const renderForm = (
    <div className="form">
      <form action="" onSubmit={handleSubmit}>
        <div className="input-container">
          <label for="inp" class="inp">
            <input
              type="text"
              id="inp"
              placeholder=" "
              name="uname"
              required
            />
            <span class="label">{t("login.labelUser")}</span>
            <span class="focus-bg"></span>
          </label>

          {renderErrorMessage("uname")}
        </div>

        <div className="input-container">
          <label for="inp" class="inp">
            <input
              id="inp"
              placeholder=" "
              type="password"
              name="pass"
              required
            />
            <span class="label">{t("login.password")}</span>
            <span class="focus-bg"></span>
          </label>

          {renderErrorMessage("pass")}
        </div>
        <div className="button-container">
          <input type="submit" value={t("login.submit")} />
        </div>
      </form>
    </div>
  );

  return (
    <div className="app">
      <div className="login-form">
        {Object.keys(lngs).length > 0 && (
          <div className="languageSelectLogin">
            <select
              value={lng}
              onChange={(e) => changeLanguage(e.target.value)}
              className="languageButtonLogin"
            >
              {Object.keys(lngs).map((lngKey) => (
                <option key={lngKey} value={lngKey}>
                  {lngs[lngKey].nativeName}
                </option>
              ))}
            </select>
          </div>
        )}

        <img className="pangeaLogo" src={pangeaLogo} alt="pangeaLogo" />
        <div className="title">{t("login.welcomeMessage")}</div>
        <div className="project-name">{t("login.projectName")}</div>
        <div className="login-please">{t("login.loginPlease")}</div>
        {isSubmitted ? <Navigate to="/monitoringoverview" /> : renderForm}
      </div>
    </div>
  );
}

export default LoginScreen;



i know its kind of a mess but i need to make it work soon and ill fix it up after it works, i think it might be the outlet object im redirecting to in PrivateRoutes since i console logged the value of storedLoginStatus and it seems to be true as it should and redirect to outlet which should be the current page and not the "not-logged-in" one.

any ideas? maybe i should implement session state and resolve this whole issue?

Thanks a lot in advance

答案1

得分: 1

# 问题

我在代码中看到的明显问题

1. 你正在将每个路由与 `NavbarProvider` 组件包装在一起因此它们各自具有独立的状态
2. 这些 `NavbarProvider` 实例中的 `loginStatus` 最初是 `false`因此在尝试访问受保护的路由时将使用此值进行初始渲染
3. 看起来你试图使用 localStorage 作为应用状态的真实来源但它应该只是状态持久化以便稍后检索例如在后续页面加载或应用挂载时
4. `ProtectedRoutes` 不应该是尝试在 `NavbarProvider` 组件中设置身份验证状态的组件它只应该读取它并相应地采取行动
5. `ProtectedRoutes` 组件没有重定向到有效路径

# 解决方案

 `NavbarProvider` 中从 localStorage 初始化 `loginStatus` 状态对索引状态执行相同操作使用 `useMemo` 钩子来计算派生的 `navbarData` "state" 实际上每当你发现自己编写了一个 `useState`/`useEffect` 钩子耦合时应该改用 `useMemo` 钩子

```jsx
export const NavbarProvider = ({ children }) => {
  // 从 localStorage 初始化
  const [loginStatus, setLoginStatus] = useState(() => {
    return !!JSON.parse(localStorage.getItem("loginStatus"));
  });
  const [selectedProjectIndex, setSelectedProjectIndex] = useState(() => {
    const storedProjectIndex = JSON.parse(
      localStorage.getItem("selectedProjectIndex")
    );

    if (storedProjectIndex !== null) {
      const parsedIndex = Number(storedProjectIndex);
      if (
        !isNaN(parsedIndex) &&
        parsedIndex >= 0 &&
        parsedIndex < NavbarData.length
      ) {
        return parsedIndex;
      }
    }
    return 0;
  });

  // 将本地状态更新持久化到 localStorage
  useEffect(() => {
    localStorage.setItem("selectedProjectIndex", selectedProjectIndex.toString());
  }, [selectedProjectIndex]);

  useEffect(() => {
    localStorage.setItem("loginStatus", JSON.stringify(loginStatus));
  }, [loginStatus]);

  const navbarData = useMemo(() => {
    return NavbarData[selectedProjectIndex]?.tabs || [];
  }, [selectedProjectIndex]);

  return (
    <NavbarContext.Provider
      value={{
        navbarData,
        selectedProjectIndex,
        setSelectedProjectIndex,
        setLoginStatus,
        loginStatus,
      }}
    >
      {children}
    </NavbarContext.Provider>
  );
};

更新 PrivateRoutes 以读取上下文中的 loginStatus 值并相应地进行重定向。重定向到有效路由,例如登录页面的 "/"

const PrivateRoutes = () => {
  const { loginStatus } = useContext(NavbarContext);

  return loginStatus ? <Outlet /> : <Navigate to="/" replace />;
};

export default PrivateRoutes;

重构路由,以便只渲染一个 NavbarProvider 并维护单个应用程序身份验证状态。

const NavbarProviderLayout = (
  <NavbarProvider>
    <Outlet />
  </NavbarProvider>
);

const NavbarLayout = (
  <>
    <Navbar />
    <PrivateRoutes />
  </>
);

const router = createBrowserRouter([
  {
    element: <NavbarProviderLayout />,
    children: [
      { path: "/", element: <LoginScreen /> },
      { path: "*", element: <NotLogged /> },
      {
        element: <NavbarLayout />,
        children: [
          { path: "monitoringoverview", element: <MonitoringOverview /> },
          { path: "performancequality", element: <PerformanceQuality /> },
          { path: "finance", element: <Finance /> },
          { path: "simulation", element: <Simulation /> },
          { path: "userconfig", element: <UserConfig /> },
          { path: "fraud", element: <Fraud /> },
          { path: "deployment", element: <Deployment /> },
          { path: "planning", element: <Planning /> },
          { path: "settings", element: <Settings /> },
          { path: "cashflowoverview", element: <CashflowOverview /> },
          // Elections
          { path: "elections", element: <Elections /> },
          { path: "whatif", element: <Whatif /> },
          //
          { path: "overview", element: <Overview /> },
          { path: "resourcemanagement", element: <ResourceManagement /> },
          // SuperGaz
          { path: "energymonitoring", element: <EnergyMonitoring /> },
          //
          { path: "monitoring", element: <Monitoring /> },
          { path: "inquiry", element: <Inquiry /> },
        ],
      },
    ],
  },
]);

createRoot(document.getElementById("root")).render(
  <RouterProvider router={router} />
);
英文:

Issues

The overt issues I see with the code:

  1. You are wrapping individual routes with the NavbarProvider component so they each have their own independent state.
  2. loginStatus is initially false in these NavbarProvider instances, so this is the value that will be used on the initial render when trying to access protected routes.
  3. It appears you are trying to use localStorage as the source of truth for your app state when it should simply be where state is persisted to be later retrieved from, e.g. a later page load/app mounting.
  4. ProtectedRoutes shouldn't be the component trying to set the auth state in the NavbarProvider component, it should only read it and act accordingly.
  5. The ProtectedRoutes component doesn't redirect to a valid path.

Solution

Initialize the loginStatus state from localStorage in NavbarProvider. Do the same for the index state. Use the useMemo hook to compute the derived navbarData "state" value. In fact, just about any time you see that you've coded a useState/useEffect hook coupling you should use the useMemo hook instead.

export const NavbarProvider = ({ children }) =&gt; {
  // Initialize from localStorage
  const [loginStatus, setLoginStatus] = useState(() =&gt; {
    return !!JSON.parse(localStorage.getItem(&quot;loginStatus&quot;));
  });
  const [selectedProjectIndex, setSelectedProjectIndex] = useState(() =&gt; {
    const storedProjectIndex = JSON.parse(
      localStorage.getItem(&quot;selectedProjectIndex&quot;)
    );

    if (storedProjectIndex !== null) {
      const parsedIndex = Number(storedProjectIndex);
      if (
        !isNaN(parsedIndex) &amp;&amp;
        parsedIndex &gt;= 0 &amp;&amp;
        parsedIndex &lt; NavbarData.length
      ) {
        return parsedIndex;
      }
    }
    return 0;
  });

  // Persist local state updates to localStorage
  useEffect(() =&gt; {
    localStorage.setItem(&quot;selectedProjectIndex&quot;, selectedProjectIndex.toString());
  }, [selectedProjectIndex]);

  useEffect(() =&gt; {
    localStorage.setItem(&quot;loginStatus&quot;, JSON.stringify(loginStatus));
  }, [loginStatus]);

  const navbarData = useMemo(() =&gt; {
    return NavbarData[selectedProjectIndex]?.tabs || [];
  }, [selectedProjectIndex]);

  return (
    &lt;NavbarContext.Provider
      value={{
        navbarData,
        selectedProjectIndex,
        setSelectedProjectIndex,
        setLoginStatus,
        loginStatus,
      }}
    &gt;
      {children}
    &lt;/NavbarContext.Provider&gt;
  );
};

Update PrivateRoutes to read the loginStatus context value and redirect accordingly. Redirect to a valid route, e.g. &quot;/&quot; for the login page.

const PrivateRoutes = () =&gt; {
  const { loginStatus } = useContext(NavbarContext);

  return loginStatus ? &lt;Outlet /&gt; : &lt;Navigate to=&quot;/&quot; replace /&gt;;
};

export default PrivateRoutes;

Restructure the routes so there is only one rendered NavbarProvider and single app auth state being maintained.

const NavbarProviderLayout = (
  &lt;NavbarProvider&gt;
    &lt;Outlet /&gt;
  &lt;/NavbarProvider&gt;
);

const NavbarLayout = (
  &lt;&gt;
    &lt;Navbar /&gt;
    &lt;PrivateRoutes /&gt;
  &lt;/&gt;
);

const router = createBrowserRouter([
  {
    element: &lt;NavbarProviderLayout /&gt;,
    children: [
      { path: &quot;/&quot;, element: &lt;LoginScreen /&gt; },
      { path: &quot;*&quot;, element: &lt;NotLogged /&gt; },
      {
        element: &lt;NavbarLayout /&gt;,
        children: [
          { path: &quot;monitoringoverview&quot;, element: &lt;MonitoringOverview /&gt; },
          { path: &quot;performancequality&quot;, element: &lt;PerformanceQuality /&gt; },
          { path: &quot;finance&quot;, element: &lt;Finance /&gt; },
          { path: &quot;simulation&quot;, element: &lt;Simulation /&gt; },
          { path: &quot;userconfig&quot;, element: &lt;UserConfig /&gt; },
          { path: &quot;fraud&quot;, element: &lt;Fraud /&gt; },
          { path: &quot;deployment&quot;, element: &lt;Deployment /&gt; },
          { path: &quot;planning&quot;, element: &lt;Planning /&gt; },
          { path: &quot;settings&quot;, element: &lt;Settings /&gt; },
          { path: &quot;cashflowoverview&quot;, element: &lt;CashflowOverview /&gt; },
          // Elections
          { path: &quot;elections&quot;, element: &lt;Elections /&gt; },
          { path: &quot;whatif&quot;, element: &lt;Whatif /&gt; },
          //
          { path: &quot;overview&quot;, element: &lt;Overview /&gt; },
          { path: &quot;resourcemanagement&quot;, element: &lt;ResourceManagement /&gt; },
          // SuperGaz
          { path: &quot;energymonitoring&quot;, element: &lt;EnergyMonitoring /&gt; },
          //
          { path: &quot;monitoring&quot;, element: &lt;Monitoring /&gt; },
          { path: &quot;inquiry&quot;, element: &lt;Inquiry /&gt; },
        ],
      },
    ],
  },
]);

createRoot(document.getElementById(&quot;root&quot;)).render(
  &lt;RouterProvider router={router} /&gt;
);

huangapple
  • 本文由 发表于 2023年7月3日 16:37:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76603117.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定