You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复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捕获异常,起初有效,但后续再次打开网站时点击登出无法触发页面刷新,需定位问题并提供修复方案。


问题分析
  1. 错误根源:handleLogout定义在组件外部,组件渲染前就会被解析,在某些场景(如SSR初始化、组件上下文未完全加载)下,全局window可能无法正常访问;同时组件的props.window与全局window重名,可能干扰全局对象的访问。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 01:43:12