如何修复Cannot read properties of undefined (reading 'location')错误?
问题描述
开发餐饮点餐系统时,用户/管理员登录后将authToken存入sessionStorage,餐厅商家登录存入restToken。登出逻辑为移除对应sessionStorage项后调用window.location.reload(true)刷新页面,该功能在首页正常运行,但在/users/dashboard和/restaurant/dashboard路由页面执行时出现如下错误:
Cannot read properties of undefined (reading 'location')
TypeError: Cannot read properties of undefined (reading 'location')
相关代码如下:
RestaurantDash组件代码
import styles from "./dashboard.module.css" import Button from '@mui/material/Button'; import Box from '@mui/material/Box'; import Toolbar from '@mui/material/Toolbar'; import MenuIcon from '@mui/icons-material/Menu'; import SettingsIcon from '@mui/icons-material/Settings'; import IconButton from '@mui/material/IconButton'; import HistoryIcon from '@mui/icons-material/History'; import LogoutIcon from '@mui/icons-material/Logout'; import Drawer from '@mui/material/Drawer'; import { useState } from "react"; import { useNavigate } from "react-router"; import { useSearchParams } from 'react-router-dom'; const drawerWidth = 240; const handleLogout = () => { sessionStorage.removeItem('restToken'); // Reload the page try { window.location.reload(true); } catch (err) { console.log(err); } }; // Attach a confirmation message when the user tries to leave the page function RestaurantDash(props) { const { window } = props; const [searchParams, setSearchParams] = useSearchParams(); const [mobileOpen, setMobileOpen] = useState(false); const navigate = useNavigate(); const handleDrawerToggle = () => { setMobileOpen((prevState) => !prevState); }; // const dispatch = useDispatch(); // const token = sessionStorage.getItem('authToken'); const drawer = ( <Box onClick={handleDrawerToggle} className={styles.drawerContainer} sx={{ height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}> <p className={styles.admin}>Restaurant Controls</p> <Button key='drawer1' sx={{ width: '100%', marginTop: '10px', height: '50px' }} color="secondary" startIcon={<SettingsIcon />} onClick={() => setSearchParams({ res: 'products' })}> Products </Button> <Button key='drawer2' sx={{ width: '100%', marginTop: '10px', height: '50px' }} color="secondary" startIcon={<HistoryIcon />} onClick={() => setSearchParams({ res: 'orders' })}> Orders </Button> <Button key='drawer3' sx={{ width: '100%', marginTop: '10px', height: '50px' }} color="secondary" startIcon={<LogoutIcon />} onClick={() => handleLogout()} > Logout </Button> </Box> ); const container = window !== undefined ? () => window().document.body : undefined; // async function getDetails() { // const res = await fetch('http://192.168.18.139:3001/admin/admin-dashboard', { // method: 'GET', // headers: { // 'Authorization': token, // 'Content-Type': 'application/json', // } // }); // const data = await res.json(); // console.log(data); // } return ( <div className={styles.dashboardContainer}> <div className={styles.heading}> <h1 onClick={() => navigate('/')}> Foody.io </h1> <div> Eat That Foody. </div> </div> <div className={styles.btnContainer}> <Toolbar sx={{ width: '10' }}> <IconButton color="inherit" aria-label="open drawer" edge="start" onClick={handleDrawerToggle} sx={{ display: { md: 'none' } }} > <MenuIcon /> </IconButton> <Box sx={{ display: { xs: 'none', sm: 'none', md: 'block' }, width: '1' }}> <p className={styles.admin}>Restaurant Controls</p> <Button key='drawer1' sx={{ width: '100%', marginTop: '10px', height: '50px' }} color="secondary" startIcon={<SettingsIcon />} onClick={() => setSearchParams({ res: 'products' })}> Products </Button> <Button key='drawer2' sx={{ width: '100%', marginTop: '10px', height: '50px' }} color="secondary" startIcon={<HistoryIcon />} onClick={() => setSearchParams({ res: 'orders' })}> Orders </Button> <Button key='drawer3' sx={{ width: '100%', marginTop: '10px', height: '50px' }} color="secondary" startIcon={<LogoutIcon />} onClick={() => handleLogout()} > Logout </Button> </Box> </Toolbar> <Drawer container={container} variant="temporary" open={mobileOpen} key="right" anchor="right" onClose={handleDrawerToggle} ModalProps={{ keepMounted: true, // Better open performance on mobile. }} sx={{ display: { sm: 'block', md: 'none' }, '& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth }, }} > {drawer} </Drawer> </div> </div> ) } export default RestaurantDash
App.js路由配置
<BrowserRouter> <Routes> <Route index element={<Home />} /> <Route path="register" element={sessionStorage.getItem('authToken') !== null ? <Navigate to="/" /> : <Register />} /> <Route path="login" element={sessionStorage.getItem('authToken') !== null ? <Navigate to="/" /> : <Login />} /> <Route path="users/dashboard" element={sessionStorage.getItem('authToken') !== null ? <UserManagement /> : <Navigate to="/login" />} /> <Route path="restaurant/dashboard" element={sessionStorage.getItem('restToken') !== null ? <RestaurantManagement /> : <Navigate to="/restaurant/login" />} /> <Route path="restaurant/login" element={sessionStorage.getItem('restToken') === null ? <RestaurantLogin /> : <Navigate to="/restaurant/dashboard" />} /> </Routes> </BrowserRouter>
曾尝试用try-catch捕获异常,起初有效,但后续再次打开网站时点击登出无法触发页面刷新,需定位问题并提供修复方案。
问题分析
- 错误根源:
handleLogout定义在组件外部,组件渲染前就会被解析,在某些场景(如SSR初始化、组件上下文未完全加载)下,全局window可能无法正常访问;同时组件的props.window与全局window重名,可能干扰全局对象的访问。 - try-catch失效原因:移除token后,路由守卫会立即触发跳转,导致页面刷新逻辑被中断,后续再次打开时,缓存或路由判断逻辑可能阻止刷新执行。
修复方案
方案1:将登出函数移至组件内部,避免全局作用域问题
把handleLogout放在组件内部,确保在React渲染上下文内访问全局window,同时重命名props中的window避免冲突:
function RestaurantDash(props) { // 重命名props的window,避免和全局window冲突 const { window: propWindow } = props; const [searchParams, setSearchParams] = useSearchParams(); const [mobileOpen, setMobileOpen] = useState(false); const navigate = useNavigate(); // 移到组件内部的登出函数 const handleLogout = () => { sessionStorage.removeItem('restToken'); try { window.location.reload(true); } catch (err) { console.log(err); // 降级方案:用路由跳转替代刷新 navigate('/restaurant/login', { replace: true }); } }; // ... 其余代码保持不变 }
方案2:改用路由跳转替代页面刷新(推荐)
SPA应用中优先使用路由导航而非直接刷新页面,彻底避免window.location的访问问题:
const handleLogout = () => { sessionStorage.removeItem('restToken'); // 使用replace模式,防止用户回退到仪表盘页面 navigate('/restaurant/login', { replace: true }); };
方案3:优化路由守卫逻辑,避免初始判断时机问题
当前路由直接在element属性中判断sessionStorage,可能存在初始化时机问题,改用自定义守卫组件:
// 自定义受保护路由组件 const ProtectedRoute = ({ children, tokenKey, redirectTo }) => { const navigate = useNavigate(); useEffect(() => { const token = sessionStorage.getItem(tokenKey); if (!token) { navigate(redirectTo, { replace: true }); } }, [navigate, tokenKey, redirectTo]); return children; }; // 修改App.js路由配置 <BrowserRouter> <Routes> <Route index element={<Home />} /> <Route path="register" element={ sessionStorage.getItem('authToken') !== null ? <Navigate to="/" /> : <Register /> } /> <Route path="login" element={ sessionStorage.getItem('authToken') !== null ? <Navigate to="/" /> : <Login /> } /> <Route path="users/dashboard" element={ <ProtectedRoute tokenKey="authToken" redirectTo="/login"> <UserManagement /> </ProtectedRoute> } /> <Route path="restaurant/dashboard" element={ <ProtectedRoute tokenKey="restToken" redirectTo="/restaurant/login"> <RestaurantManagement /> </ProtectedRoute> } /> <Route path="restaurant/login" element={ sessionStorage.getItem('restToken') === null ? <RestaurantLogin /> : <Navigate to="/restaurant/dashboard" /> } /> </Routes> </BrowserRouter>
总结
优先采用方案2,符合SPA路由逻辑,彻底规避window访问问题;配合方案3优化路由守卫,保证页面跳转的可靠性。若必须保留页面刷新逻辑,使用方案1确保window的正确访问。
内容的提问来源于stack exchange,提问作者humayunraza
相关产品推荐
相关产品推荐

