英文:
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:
- 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
- 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.
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”组件不会重新渲染。
以下是我从视频中了解到的事件时间线:
- “App”挂载并从localStorage中读取“loginUser”。由于“loginUser”为假值,不会向存储分派任何操作,并且用户未经身份验证。
- 用户在“NavBar”中单击“登录”并导航到“/auth”,由于“loginUser”为假值,因此渲染了“Auth”组件。
- 用户成功进行身份验证,并且应用程序导航到“/posts”。
- 用户在“NavBar”中单击“登出”,成功注销。Redux状态已更新并清除了localStorage。重新渲染了“NavBar”并再次显示“登录”按钮。
- 用户在“NavBar”中单击“登录”并导航到“/auth”,由于“loginUser”(本地副本)仍然为假值,因此渲染了“Auth”组件。
- 用户成功进行身份验证,并且应用程序导航到“/posts”。
- 活动选项卡关闭,然后打开新选项卡,挂载了“App”并从localStorage中读取“loginUser”,由于用户已经经过身份验证,“loginUser”为真值,Redux存储已更新。重新渲染了“NavBar”并显示“登出”按钮。
- 用户在“NavBar”中单击“登出”,成功注销。Redux状态已更新并清除了localStorage。重新渲染了“NavBar”并再次显示“登录”按钮。
- 用户在“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:
App
mounts andloginUser
is read from localStorage. SinceloginUser
is falsey, no action is dispatched to the store and the user is unauthenticated.- User clicks "Log In" in
NavBar
and the app navigates to"/auth"
and sinceloginUser
is falsey, theAuth
component is rendered. - User authenticates successfully and the app navigates to
"/posts"
. - User clicks "Log Out" in
NavBar
and is successfully logged out. The redux state it updated and the localStorage is cleared out. TheNavBar
rerenders and displays "Log In" button again. - User clicks "Log In" in
NavBar
and the app navigates to"/auth"
and sinceloginUser
(the local copy) is still falsey, theAuth
component is rendered. - User authenticates successfully and the app navigates to
"/posts"
. - The active tab is closed and a new tab opened and
App
is mounted andloginUser
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. - User clicks "Log Out" in
NavBar
and is successfully logged out. The redux state it updated and the localStorage is cleared out. TheNavBar
rerenders and displays "Log In" button again. - User clicks "Log In" in
NavBar
and the app navigates to"/auth"
, and sinceloginUser
(the local copy) is still truthy, theHome
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("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>
</>
);
};
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论