路由导航时useContext变量重置为undefined的问题排查
React useContext 路由导航后状态重置为undefined问题排查与解决
问题描述
在React项目中使用useContext管理全局用户状态,登录成功后设置状态能正常读取,但通过FMApp组件内导航栏跳转路由后,appUserCtx会变为undefined,无法获取之前设置的上下文值。
相关代码片段
App.js
function App() { const [appUserCtx, setAppUserCtx] = useState(); return ( <AppUserContext.Provider value={{ appUserCtx, setAppUserCtx }}> <Router> <Switch> <Route path={['/admin/*', '/admin']} component={Admin} /> /**Login */ <Route exact path="/fmApp" component={Login} /> <Route exact path={'/fmApp/customer/:id'} component={CustomerLogin} /> <Route path={'/fmApp/*'} component={FMApp} /> <Route exact path={Constants.ERROR_PAGE} component={ErrorPage} /> </Switch> </Router > </AppUserContext.Provider> ); }
AppUserContext.js
import { createContext, useState } from "react"; const AppUserContext = createContext({}); export default AppUserContext;
useAuth.js
import { useContext, useDebugValue } from "react"; import AppUserContext from "./context/AppUserContext"; const useAuth = () => { return useContext(AppUserContext); } export default useAuth;
CustomerLogin.js 登录状态设置代码
UserService.verifyLogin(axiosPrivate, appUser). then(response => { localStorage.setItem('authUser', JSON.stringify(response.data)); setAppUser(response.data); setAppUserCtx(response.data); setSubmitted(false); props.history.push({ pathname: "/fmApp/dailySalesList" }); }).catch(e => { setSubmitted(false); setModelVariant('error') setMessage(e.response.data); autoRef.current.setModelOpen(true); })
FMApp.js 导航组件代码
const FMApp = (props) => { const history = useHistory(); const rediretToAmdin = () => { window.location.href = "/admin"; } const {appUserCtx, setAppUserCtx} = useAuth(); return ( <div> <Navbar className='navbar' variant="dark" expand="lg" sticky="top"> <Navbar.Brand href="#home" className='p-0' style={{marginTop:'-12px'}}> <FMLogo height='30'/></Navbar.Brand> <Navbar.Toggle aria-controls="basic-navbar-nav" /> <Navbar.Collapse id="basic-navbar-nav"> <Nav className="mr-auto"> <NavDropdown title="Stocks" id="basic-nav-dropdown"> <NavDropdown.Item href="/fmApp/fuelStockList">Fuel Stock</NavDropdown.Item> </NavDropdown> <div><Logout className="float-left" /></div> </Nav> {/* show admin access link only if user role is as admin enabled*/} {appUserCtx.userRoleMO?.isAdmin === 1 && <div> <StylesProvider injectFirst> <Link className="adminLink" onClick={rediretToAmdin}>Go to admin</Link></StylesProvider></div>} <div> <ROHeader></ROHeader></div> </Navbar.Collapse> </Navbar> <div className="container mt-3"> <Switch> //stock <Route exact path="/fmApp/fuelStockList" component={FuelStockList} /> </Switch> </div> </div> ); } export default FMApp;
问题原因
- 状态未持久化:useContext + useState的状态仅存在于内存中,页面刷新会导致App组件重新挂载,状态重置为初始值
undefined。 - 错误的导航方式:
rediretToAmdin使用window.location.href跳转,会触发页面全量刷新,丢失内存中的全局状态。 - 初始状态未读取本地存储:App组件初始化时未从localStorage读取已保存的用户信息,刷新后状态为空。
- 导航栏链接触发页面刷新:
NavDropdown.Item的href属性会触发浏览器原生跳转,导致页面刷新。
解决方案
1. 初始化时从localStorage恢复状态
修改App.js,在useState初始化时读取localStorage中的用户数据:
function App() { // 从localStorage读取已保存的用户,作为初始状态 const initialUser = localStorage.getItem('authUser') ? JSON.parse(localStorage.getItem('authUser')) : undefined; const [appUserCtx, setAppUserCtx] = useState(initialUser); return ( <AppUserContext.Provider value={{ appUserCtx, setAppUserCtx }}> <Router> <Switch> <Route path={['/admin/*', '/admin']} component={Admin} /> <Route exact path="/fmApp" component={Login} /> <Route exact path={'/fmApp/customer/:id'} component={CustomerLogin} /> <Route path={'/fmApp/*'} component={FMApp} /> <Route exact path={Constants.ERROR_PAGE} component={ErrorPage} /> </Switch> </Router > </AppUserContext.Provider> ); }
2. 使用React Router导航替代原生跳转
修改FMApp中的rediretToAmdin函数,用history.push代替window.location.href,避免页面刷新:
const FMApp = (props) => { const history = useHistory(); const rediretToAmdin = () => { // 替换为React Router的导航方法 history.push("/admin"); } const {appUserCtx, setAppUserCtx} = useAuth(); return ( // 其余代码保持不变 ); }
3. 替换导航栏链接为React Router的Link组件
修改FMApp中的NavDropdown.Item,使用React Router的Link组件避免页面刷新:
// 先导入Link组件 import { Link } from 'react-router-dom'; // 替换原有NavDropdown.Item <NavDropdown.Item as={Link} to="/fmApp/fuelStockList">Fuel Stock</NavDropdown.Item>
4. 可选:封装状态更新逻辑,自动同步localStorage
在useAuth钩子中封装状态更新函数,确保状态变更时自动同步到localStorage:
import { useContext, useCallback } from "react"; import AppUserContext from "./context/AppUserContext"; const useAuth = () => { const { appUserCtx, setAppUserCtx } = useContext(AppUserContext); // 封装带持久化的状态更新函数 const setAuthUser = useCallback((user) => { setAppUserCtx(user); user ? localStorage.setItem('authUser', JSON.stringify(user)) : localStorage.removeItem('authUser'); }, [setAppUserCtx]); return { appUserCtx, setAuthUser }; } export default useAuth;
然后在CustomerLogin中使用封装后的setAuthUser:
// 导入useAuth并获取setAuthUser const { setAuthUser } = useAuth(); UserService.verifyLogin(axiosPrivate, appUser). then(response => { setAppUser(response.data); setAuthUser(response.data); // 使用封装后的函数 setSubmitted(false); props.history.push({ pathname: "/fmApp/dailySalesList" }); }).catch(e => { // 错误处理代码保持不变 })
内容的提问来源于stack exchange,提问作者pappu_kutty
相关产品推荐
相关产品推荐

