逻辑检查登录状态以渲染路由组件(react-router-dom)的部分不正常工作。

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

Logic to check login status to render component in Route (react-router-dom) not working properly

问题

I've reviewed the code and the issue you're facing. It appears that you are storing user information in local storage and using Redux to manage the user's authentication status. However, there is a potential problem with how you handle user authentication and logout, which could be causing the issue you described.

In your reducer, when you handle authentication (AUTH action), you are storing the user profile in local storage and updating the Redux state. However, when you handle logout (LOGOUT action), you are only removing the user profile from local storage and not updating the Redux state. This could lead to inconsistencies between local storage and Redux state.

Here's what you can do to improve this:

  1. When handling the LOGOUT action, make sure to clear both local storage and the Redux state to ensure consistency:
case LOGOUT:
  localStorage.removeItem("profile");
  return {}; // Clear the Redux state as well
  1. Additionally, in your App component, when checking the local storage for the user profile, you should also handle the case when the user logs out. If the user logs out, you should clear the local storage and the Redux state to prevent any inconsistencies:
useEffect(() => {
  const loginUser = JSON.parse(localStorage.getItem("profile"));
  if (loginUser) {
    dispatch({ type: AUTH, payload: loginUser });
  } else {
    // If there is no user in local storage, clear the Redux state
    dispatch({ type: LOGOUT });
  }
}, []);

By making these changes, you ensure that the local storage and Redux state are in sync when the user logs out, which should resolve the issue you're experiencing when reopening the tab and trying to access the auth page.

Please implement these changes and test your application again to see if the issue is resolved.

英文:

I'm starting to learn react and I have a problem related to checking user's login status. I check the user's login status by getting the user info from localStorage. If the user is logged in, I will redirect the user back to the home page. Everything works great but in case the user closes the tab and comes back, if the user was previously logged in and then logged out, when clicking the button to go to the auth page, even though the url is set to "/auth", it seems that the value of loginUser in this case is wrong and the Route render the <Home /> instead of <Auth />. I don't understand why it happens in case the user closes the tab and comes back.

Here is my App.js code.

const App = () => {
  const loginUser = JSON.parse(localStorage.getItem("profile"));
  const dispatch = useDispatch();

  // I use redux's global state to store user info and render components corresponding to that state. 
  // So when starting the app check the local storage and sync it with the redux state
  useEffect(() => {
    if (loginUser) {
      dispatch({ type: AUTH, payload: loginUser });
    }
  }, []);

  return (
    <>
      <Helmet>
        <meta
          http-equiv="Cross-Origin-Opener-Policy"
          content="same-origin-allow-popups"
        />
      </Helmet>
      <Router>
        <Container style={{ marginBottom: "150px" }} maxWidth="xl">
          <NavBar />
          <Routes>
            <Route path="/" exact element={<Navigate to={"/posts"} />} />
            <Route path="/posts" exact element={<Home />} />
            <Route path="/posts/search" exact element={<Home />} />
            <Route path="/posts/:id" exact element={<PostDetails />} />
            <Route path="/auth" element={!loginUser ? <Auth /> : <Home />} />
          </Routes>
        </Container>
      </Router>
    </>
  );
};

Here is my Navbar that renders a Login and Logout button according to the state "profile" that I use to store login user info.

export default function NavBar() {
  const classes = useStyle();
  const navigate = useNavigate();
  const dispatch = useDispatch();
  const [messageApi, contextHolder] = message.useMessage();
  const user = useSelector((state) => state.profile);

  const logout = (e) => {
    e.preventDefault();
    dispatch({ type: LOGOUT });
    messageApi.success("Logged out.");
  };

  return (
    <>
      {contextHolder}
      <AppBar className={classes.appBar} position="static" color="inherit">
        <div className={classes.brandContainer}>
          <Typography
            to="/"
            component={Link}
            className={classes.heading}
            variant="h2"
            align="center"
          >
            My app
          </Typography>
          <img
            className={classes.image}
            src={memoriesImg}
            alt="memories"
            height="60"
          />
        </div>
        <Toolbar>
          {Object.keys(user).length !== 0 ? (
            <div className={classes.profile}>
              <Avatar
                className={classes.purple}
                alt={user?.name}
                src={user?.picture}
              >
                {user?.name?.charAt(0)}
              </Avatar>
              <Typography className={classes.userName} variant="h6">
                {user?.name}
              </Typography>
              <Button
                onClick={(e) => logout(e)}
                variant="contained"
                className={classes.logout}
                color="secondary"
              >
                Logout
              </Button>
            </div>
          ) : (
            <Button
              component={Link}
              to="/auth"
              variant="contained"
              color="primary"
            >
              Login
            </Button>
          )}
        </Toolbar>
      </AppBar>
    </>
  );
}

Here is what happens inside reducers when user login and logout

export default (profile = {}, action) => {
  switch (action.type) {
    case AUTH:
      localStorage.setItem("profile", JSON.stringify({ ...action.payload }));
      return { ...profile, ...action.payload.profile };
    case LOGOUT:
      localStorage.removeItem("profile");
      return {};
    case "TEST":
      console.log("dispatch success TEST from axios");
      return profile;
    default:
      return profile;
  }
};

I also recorded a short video where the first part shows when I log in, log out, and go to the auth page normally. However when I close the tab and open a new tab, then after log out I can't go to the auth page anymore.

https://somup.com/c0iIQRzu64

I want someone to check if it's reasonable for me to store user information and handle it like that. And if there is something wrong somewhere.

答案1

得分: 0

问题

基本上问题是Redux状态与localStorage没有很好地耦合在一起。当身份验证状态更改时,“App”组件不会重新渲染。

以下是我从视频中了解到的事件时间线:

  1. “App”挂载并从localStorage中读取“loginUser”。由于“loginUser”为假值,不会向存储分派任何操作,并且用户未经身份验证。
  2. 用户在“NavBar”中单击“登录”并导航到“/auth”,由于“loginUser”为假值,因此渲染了“Auth”组件。
  3. 用户成功进行身份验证,并且应用程序导航到“/posts”。
  4. 用户在“NavBar”中单击“登出”,成功注销。Redux状态已更新并清除了localStorage。重新渲染了“NavBar”并再次显示“登录”按钮。
  5. 用户在“NavBar”中单击“登录”并导航到“/auth”,由于“loginUser”(本地副本)仍然为假值,因此渲染了“Auth”组件。
  6. 用户成功进行身份验证,并且应用程序导航到“/posts”。
  7. 活动选项卡关闭,然后打开新选项卡,挂载了“App”并从localStorage中读取“loginUser”,由于用户已经经过身份验证,“loginUser”为真值,Redux存储已更新。重新渲染了“NavBar”并显示“登出”按钮。
  8. 用户在“NavBar”中单击“登出”,成功注销。Redux状态已更新并清除了localStorage。重新渲染了“NavBar”并再次显示“登录”按钮。
  9. 用户在“NavBar”中单击“登录”并导航到“/auth”,由于“loginUser”(本地副本)仍然为真值,因此渲染了“Home”组件。应用程序处于“已认证”状态。

为了解决这个问题,Redux状态应该从localStorage中初始化,并且在“App”中的“loginUser”应该选择来自存储的状态,就像在“NavBar”组件中一样。这样,当Redux存储中的身份验证状态更新时,“App”将使用当前选择的Redux状态值重新渲染。

示例:

const initialState = JSON.parse(localStorage.getItem("profile")) || {};

export default (state = initialState, action) => {
  switch (action.type) {
    case AUTH:
      localStorage.setItem("profile", JSON.stringify({ ...action.payload }));
      return {
        ...state,
        ...action.payload
      };

    case LOGOUT:
      localStorage.removeItem("profile");
      return {};

    case "TEST":
      console.log("dispatch success TEST from axios");
      return state;

    default:
      return state;
  }
};
const App = () => {
  const user = useSelector((state) => state.profile);
  
  return (
    <>
      <Helmet>
        <meta
          http-equiv="Cross-Origin-Opener-Policy"
          content="same-origin-allow-popups"
        />
      </Helmet>
      <Router>
        <Container style={{ marginBottom: "150px" }} maxWidth="xl">
          <NavBar />
          <Routes>
            <Route path="/" element={<Navigate to="/posts" replace />} />
            <Route path="/posts" element={<Home />} />
            <Route path="/posts/search" element={<Home />} />
            <Route path="/posts/:id" element={<PostDetails />} />
            <Route
              path="/auth"
              element={user
                ? <Navigate to="/posts" replace />
                : <Auth />
              }
            />
          </Routes>
        </Container>
      </Router>
    </>
  );
};
英文:

Issue

Basically the issue is that the redux state isn't coupled well to the localStorage. The App component doesn't rerender when the auth status changes.

Here's the timeline of events as I far as I can tell from the video:

  1. App mounts and loginUser is read from localStorage. Since loginUser is falsey, no action is dispatched to the store and the user is unauthenticated.
  2. User clicks "Log In" in NavBar and the app navigates to &quot;/auth&quot; and since loginUser is falsey, the Auth component is rendered.
  3. User authenticates successfully and the app navigates to &quot;/posts&quot;.
  4. User clicks "Log Out" in NavBar and is successfully logged out. The redux state it updated and the localStorage is cleared out. The NavBar rerenders and displays "Log In" button again.
  5. User clicks "Log In" in NavBar and the app navigates to &quot;/auth&quot; and since loginUser (the local copy) is still falsey, the Auth component is rendered.
  6. User authenticates successfully and the app navigates to &quot;/posts&quot;.
  7. The active tab is closed and a new tab opened and App is mounted and loginUser is read from localStorage, and since the user is authenticated, loginUser is truthy and the redux store is updated. NavBar is rerendered and a "Log Out" button is rendered.
  8. User clicks "Log Out" in NavBar and is successfully logged out. The redux state it updated and the localStorage is cleared out. The NavBar rerenders and displays "Log In" button again.
  9. User clicks "Log In" in NavBar and the app navigates to &quot;/auth&quot;, and since loginUser (the local copy) is still truthy, the Home component is rendered. The app is "stuck" in an "authenticated" state.

To resolve this the Redux state should be initialized from localStorage and loginUser in App should be selected state from the store like it is in the NavBar component. This way when the auth state in the Redux store updates, App will be rerendered with the current selected redux state value.

Example:

const initialState = JSON.parse(localStorage.getItem(&quot;profile&quot;)) || {};

export default (state = initialState, action) =&gt; {
  switch (action.type) {
    case AUTH:
      localStorage.setItem(&quot;profile&quot;, JSON.stringify({ ...action.payload }));
      return {
        ...state,
        ...action.payload
      };

    case LOGOUT:
      localStorage.removeItem(&quot;profile&quot;);
      return {};

    case &quot;TEST&quot;:
      console.log(&quot;dispatch success TEST from axios&quot;);
      return state;

    default:
      return state;
  }
};
const App = () =&gt; {
  const user = useSelector((state) =&gt; state.profile);
  
  return (
    &lt;&gt;
      &lt;Helmet&gt;
        &lt;meta
          http-equiv=&quot;Cross-Origin-Opener-Policy&quot;
          content=&quot;same-origin-allow-popups&quot;
        /&gt;
      &lt;/Helmet&gt;
      &lt;Router&gt;
        &lt;Container style={{ marginBottom: &quot;150px&quot; }} maxWidth=&quot;xl&quot;&gt;
          &lt;NavBar /&gt;
          &lt;Routes&gt;
            &lt;Route path=&quot;/&quot; element={&lt;Navigate to=&quot;/posts&quot; replace /&gt;} /&gt;
            &lt;Route path=&quot;/posts&quot; element={&lt;Home /&gt;} /&gt;
            &lt;Route path=&quot;/posts/search&quot; element={&lt;Home /&gt;} /&gt;
            &lt;Route path=&quot;/posts/:id&quot; element={&lt;PostDetails /&gt;} /&gt;
            &lt;Route
              path=&quot;/auth&quot;
              element={user
                ? &lt;Navigate to=&quot;/posts&quot; replace /&gt;
                : &lt;Auth /&gt;
              }
            /&gt;
          &lt;/Routes&gt;
        &lt;/Container&gt;
      &lt;/Router&gt;
    &lt;/&gt;
  );
};

huangapple
  • 本文由 发表于 2023年7月13日 18:10:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/76678232.html
匿名

发表评论

匿名网友

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

确定