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

React(MERN)应用管理员登出时出现user为null的类型错误求助

React(MERN)应用管理员导航链接登出时报错问题解决

问题概述

在MERN栈React应用中实现导航栏/admin链接仅对管理员(user.isAdmin === true)可见的功能,正常状态下普通用户看不到该链接、管理员可正常访问,但管理员登出时会触发Uncaught TypeError: user is null错误,普通用户登出则无此问题。


现有代码片段

import { useAuthContext } from '../hooks/useAuthContext'
import { useIsAdmin } from '../hooks/useAdmin'

const Navbar = () => {
    const [nav, setNav] = useState(false)
    const {logout} = useLogout()
    const {user} = useAuthContext()
    const isAdmin = useIsAdmin()

// 渲染部分代码
{ isAdmin ? <Link to='/admin'><li className='p-4 hover:text-[#00df9a] transition-all duration-500'>Admin {user.name}</li></Link> : <div></div> }

useAdmin自定义Hook

import { useEffect, useState } from 'react';
import { useAuthContext} from '../hooks/useAuthContext';

export function useIsAdmin() {
    const { user } = useAuthContext();
    const [isAdmin, setIsAdmin] = useState(null);
  
    useEffect(() => {
      if (user) {
        setIsAdmin(user.isAdmin && user);
      }
    }, [user]);
  
    useEffect(() => {
      if (!user || user === null) {
        setIsAdmin(null);
      }
    }, [user]);
  
    return isAdmin;
  }

AuthContext

import { createContext, useReducer, useEffect } from 'react'

export const AuthContext = createContext()

export const authReducer = (state, action) => {
  switch (action.type) {
    case 'LOGIN':
      return { user: action.payload }
    case 'LOGOUT':
      window.localStorage.clear()
      return {user: null}

    case 'DELETE_USER':
      return {...state, user: state.user.filter(u =>
         u.id !== action.payload
      )}    
    default:
      return state
  }
}

export const AuthContextProvider = ({ children }) => {
  const [state, dispatch] = useReducer(authReducer, {
  user: null
})

  useEffect(() => {
    const user = JSON.parse(localStorage.getItem('user'))

    if (user) {
      dispatch({ type: 'LOGIN', payload: user })
    }

  }, [])

  console.log('AuthContext state:', state)

  return (
    <AuthContext.Provider value={{ ...state, dispatch }}>
      { children }
    </AuthContext.Provider>
  )
}

useLogout Hook

import { useAuthContext } from './useAuthContext'

export const useLogout = () => {
    const { dispatch } = useAuthContext()
  
    const logout = () => {
      // 移除本地存储的用户信息
      localStorage.removeItem('user')
  
      // 分发登出动作
      dispatch({ type: 'LOGOUT' })
    }
  
    return { logout }
  }

问题分析

  1. 报错核心原因:管理员登出时,useIsAdmin的状态更新存在延迟,短暂保留了之前的用户对象引用,但此时AuthContext中的user已变为null。Navbar渲染时进入isAdmin分支,尝试访问user.name,触发空值类型错误。普通用户登出时isAdmin本身为null,不会进入该分支,因此无报错。
  2. useHook逻辑缺陷:useIsAdmin返回的是user.isAdmin && user(即管理员用户对象)而非布尔值,导致判断逻辑混淆;同时两个useEffect监听同一user依赖,存在逻辑重复。

解决方案

方案1:修复useAdmin Hook,返回布尔值

修改useIsAdmin,使其返回明确的布尔值,避免状态不一致:

import { useEffect, useState } from 'react';
import { useAuthContext} from '../hooks/useAuthContext';

export function useIsAdmin() {
    const { user } = useAuthContext();
    const [isAdmin, setIsAdmin] = useState(false);
  
    useEffect(() => {
      // 直接判断用户存在且为管理员
      setIsAdmin(!!user && user.isAdmin === true);
    }, [user]);
  
    return isAdmin;
  }

方案2:渲染时添加空值保护

在Navbar中使用可选链操作符?.,确保访问user.name前user存在:

{ isAdmin ? <Link to='/admin'><li className='p-4 hover:text-[#00df9a] transition-all duration-500'>Admin {user?.name}</li></Link> : <div></div> }

方案3:优化登出逻辑(可选)

当前useLogout中localStorage.clear()会清除所有本地存储数据,仅移除user项即可:

export const useLogout = () => {
    const { dispatch } = useAuthContext()
  
    const logout = () => {
      localStorage.removeItem('user')
      dispatch({ type: 'LOGOUT' })
    }
  
    return { logout }
  }

内容的提问来源于stack exchange,提问作者Tomasz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 10:35:27