React登录成功后路由跳转页面未更新问题求助
咱们先拆解你遇到的核心问题:登录/登出后页面需刷新才加载、渲染时的状态更新警告,以及Routes组件只执行一次console.log('inside route')的原因——这些都是状态同步和副作用执行时机的问题,完全不需要更换路由机制,调整代码就能解决。
1. 先解决「渲染时状态更新警告」
你看到的Warning: Cannot update during an existing state transition警告,是因为在Login组件的render函数里直接执行了props.history.push——render应该是纯函数,不能包含路由跳转这类副作用操作,否则会打断React的渲染流程。
修改Login组件:把跳转逻辑放到useEffect里
import React, { useEffect } from 'react'; import { shallowEqual, useSelector, useDispatch } from 'react-redux'; import FormGroup from '../../components/core/form/form-group'; import Button from '../../components/core/button'; import useForm from '../../helpers/custom-hooks'; import { signInRequest, clearActionResultAdmin } from '../../redux/actions'; import { showAlert } from '../../helpers/services/alert'; const Login = (props) => { const dispatch = useDispatch(); const actionResult = useSelector((state) => state.admin.actionResult, shallowEqual); const { handleInputChange, handleSubmit } = useForm(signInRequest); // 用useEffect监听actionResult变化,在渲染完成后执行副作用 useEffect(() => { if (actionResult && actionResult.type === 'success') { props.history.push('/categories'); // 成功跳转后清除actionResult,避免重复触发 dispatch(clearActionResultAdmin()); } else if (actionResult && actionResult.type === 'error') { showAlert({ type: actionResult.type, title: 'LOGIN FAILED', text: 'Incorrect login or password', onClose: () => dispatch(clearActionResultAdmin()), }); } }, [actionResult, dispatch, props.history]); // 添加所有依赖项,避免闭包问题 return ( <form onSubmit={handleSubmit}> <div className="login-page__container"> <FormGroup className='form-group__login'> <h3>Login</h3> <input required={true} type="text" placeholder="Login" name="login" className='form-input form-input__full' onChange={handleInputChange} /> <h3>Password</h3> <input required={true} type="password" placeholder="Password" name="password" className='form-input form-input__full' onChange={handleInputChange} /> <Button type='submit' text='Login' /> </FormGroup> </div> </form> ); }; export default Login;
2. 解决「登录/登出后页面需刷新才加载」的问题
你的Routes组件里,isLoggedIn是在组件挂载时只读取一次LocalStorage,之后即使登录成功修改了Storage,组件也不会自动重渲染——因为React组件只有在状态、props变化时才会重新渲染,LocalStorage的变化不会触发组件更新,这就是为什么console.log('inside route')只执行一次的原因。
方案一:用Redux状态管理登录状态(推荐,因为你已经在用Redux)
既然你已经用Redux管理actionResult,那直接在登录成功的reducer里添加isLoggedIn状态,然后在Routes组件中通过useSelector获取这个状态,这样状态变化时Routes会自动重渲染:
第一步:在Redux的admin reducer中添加登录状态
假设你的admin reducer是这样的,添加isLoggedIn字段:
const initialState = { actionResult: null, isLoggedIn: !!StorageManager.get('session') // 初始化时从Storage读取 }; export default function adminReducer(state = initialState, action) { switch(action.type) { case 'SIGN_IN_SUCCESS': return { ...state, isLoggedIn: true, actionResult: { type: 'success' } }; case 'SIGN_OUT_SUCCESS': return { ...state, isLoggedIn: false, actionResult: null }; // ...其他case default: return state; } }
第二步:修改Routes组件,从Redux获取登录状态
import React from 'react'; import { Switch, Route, Redirect, BrowserRouter } from 'react-router-dom'; import Home from '../components/home'; import Header from '../components/header'; import LeftSidebarMenu from '../components/left-sidebar-menu'; import CreateCategory from '../components/categories/create-category'; import MainLayout from '../layouts/main-layout'; import Login from '../components/login'; import { MAIN_PATH, CREATE_CATEGORY, LOGIN_PAGE } from '../configs/constants'; import ProtectedRoute from '../helpers/services/auth'; import { useSelector, shallowEqual } from 'react-redux'; const Routes = () => { // 从Redux获取登录状态,状态变化时自动触发重渲染 const isLoggedIn = useSelector(state => state.admin.isLoggedIn, shallowEqual); const appSwitch = () => ( <Switch> <Redirect exact from="/" to="/categories" /> <ProtectedRoute exact path={CREATE_CATEGORY} component={CreateCategory} isAuth={isLoggedIn} /> <ProtectedRoute exact path={MAIN_PATH} component={Home} isAuth={isLoggedIn} /> {/* 删掉重复的CREATE_CATEGORY路由,避免冲突 */} </Switch> ); console.log('inside route'); return ( <> <div className="background"></div> <BrowserRouter> {/* 用三元表达式替代_.cond,更易读 */} {isLoggedIn ? ( <MainLayout> <Header /> <LeftSidebarMenu /> <main className="main-container"> {appSwitch()} </main> </MainLayout> ) : ( <div className='login-page'> <Switch> <Route exact path={LOGIN_PAGE} component={Login} /> {/* 添加全局重定向:未登录时访问任何页面都跳转到登录页 */} <Redirect to={LOGIN_PAGE} /> </Switch> </div> )} </BrowserRouter> </> ); }; export default Routes;
方案二:不用Redux?监听Storage变化
如果不想依赖Redux,可以用useState+storage事件监听LocalStorage的变化,手动同步状态:
const Routes = () => { const [isLoggedIn, setIsLoggedIn] = React.useState(!!StorageManager.get('session')); // 监听Storage变化,同步状态 React.useEffect(() => { const handleStorageChange = () => { setIsLoggedIn(!!StorageManager.get('session')); }; window.addEventListener('storage', handleStorageChange); // 组件卸载时移除监听 return () => window.removeEventListener('storage', handleStorageChange); }, []); // ...其余代码和上面一致 };
最后说下登出的问题
登出时的逻辑和登录一致:确保登出操作会更新Redux的isLoggedIn状态(或者修改LocalStorage),这样Routes组件会自动重渲染,页面无需刷新就能切换回登录页。
内容的提问来源于stack exchange,提问作者Norayr Ghukasyan

