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

请求后无法访问数据 报Cannot read properties of undefined(reading 'user')

问题描述

实现浏览器刷新后保持会话登录状态功能时,拉取到的用户数据无法正常渲染,控制台抛出错误:Cannot read properties of undefined (reading 'user')
接口返回数据结构参考截图:
数据结构截图1
数据结构截图2

涉及代码如下:

Auth.js 登录/注册组件代码

const Auth = () => {
    const navigate = useNavigate();
    const dispatch = useDispatch();
    const [isSignup, setIsSignup] = useState(false);
    const [inputs, setInputs] = useState({
        name: "",
        username: "",
        email: "",
        password: ""
    })

    const handleChange = (e) => {
        setInputs(prevState => {
            return {
                ...prevState,
                [e.target.name]: e.target.value
            }
        })
    }

     const sendRequest = async (type = '') => {
        const res = await axios.post(`/user/${type}`, {
            name: inputs.name,
            email: inputs.email,
            username: inputs.username,
            password: inputs.password,
        }).catch(error => console.log(error))
        const data = await res.data;
        console.log(data)
        return data;
    }

    const handleSubmit = (e) => {
        e.preventDefault()
        console.log(inputs)
        if (isSignup) {
            sendRequest("signup")
                .then((data) => {
                dispatch(authActions.login());
                localStorage.setItem('userId', data.user._id);
                navigate("/posts");
                });
        } else {
            sendRequest("login")
                .then((data) => {
                dispatch(authActions.login());
                localStorage.setItem('userId', data.user._id);
                navigate("/posts");
                });
        }
    }

Redux Store 配置代码

const authSlice = createSlice({
    name: "auth",
    initialState: { isLoggedIn: false },
    reducers: {
        login(state) {
            state.isLoggedIn = true
        },
        logout(state) {
            state.isLoggedIn = false
        }
    }
})

export const authActions = authSlice.actions

export const store = configureStore({
    reducer: authSlice.reducer
})

问题根因
  • 错误触发直接原因:sendRequest方法用.catch直接吞掉接口错误,一旦请求失败(参数异常、服务端报错、网络不通),res会是undefined,后续执行const data = await res.data时无法拿到有效返回值,传入.then回调的data为undefined,再读取data.user就会抛出当前看到的错误。
  • 刷新保持登录逻辑缺失:Redux存储的是内存状态,页面刷新后会完全重置,当前代码只在登录时往localStorage存了userId,但应用初始化时既没有读取localStorage恢复登录标记,也没有主动拉取对应用户数据,组件渲染时自然拿不到user字段。
  • 状态设计缺陷:当前auth切片只存了isLoggedIn布尔值,没有预留用户信息存储字段,即使拉到用户数据也没有统一存储的位置,组件直接读取不存在的user属性就会报错。
  • 渲染防御缺失:组件渲染用户信息时没有做判空处理,首屏加载阶段用户数据还在请求中时,直接读取深层属性必然触发undefined错误。

修复方案

1. 修复请求方法的错误处理

修改sendRequest方法,不要吞掉错误后继续执行后续逻辑,错误分支返回明确的空值,同时去掉冗余的await res.data写法:

const sendRequest = async (type = '') => {
  try {
    const res = await axios.post(`/user/${type}`, {
      name: inputs.name,
      email: inputs.email,
      username: inputs.username,
      password: inputs.password,
    })
    return res.data;
  } catch (error) {
    console.log(error)
    // 错误时返回null,避免后续链式调用拿到undefined
    return null
  }
}

同时修改提交逻辑,增加返回值判空,登录时把用户信息一起存入Redux:

const handleSubmit = (e) => {
  e.preventDefault()
  const requestType = isSignup ? "signup" : "login"
  sendRequest(requestType).then((data) => {
    // 数据不存在时直接返回,不执行后续逻辑
    if (!data?.user) return
    // 把用户信息传入login action,存入Redux
    dispatch(authActions.login(data.user));
    localStorage.setItem('userId', data.user._id);
    navigate("/posts");
  });
}

2. 重构Redux auth切片

调整初始化逻辑,增加用户信息存储字段,补充用户信息设置、退出清理缓存的逻辑:

const authSlice = createSlice({
  name: "auth",
  initialState: { 
    // 初始化时优先读localStorage的登录标记,恢复登录态
    isLoggedIn: !!localStorage.getItem('userId'),
    // 新增user字段存储用户信息
    user: null
  },
  reducers: {
    login(state, action) {
      state.isLoggedIn = true
      state.user = action.payload
    },
    logout(state) {
      state.isLoggedIn = false
      state.user = null
      // 退出时清理本地缓存
      localStorage.removeItem('userId')
    },
    // 新增action用于刷新后拉取到用户信息时更新状态
    setUser(state, action) {
      state.user = action.payload
    }
  }
})

export const authActions = authSlice.actions

export const store = configureStore({
  reducer: authSlice.reducer
})

3. 增加应用初始化的用户信息拉取逻辑

在根组件(如App.js)或全局路由守卫中增加初始化逻辑,页面加载时如果检测到本地有userId,主动拉取用户信息存入Redux:

// App.js
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import axios from 'axios'
import { authActions } from './store/auth'

function App() {
  const dispatch = useDispatch()
  const isLoggedIn = useSelector(state => state.isLoggedIn)

  useEffect(() => {
    const userId = localStorage.getItem('userId')
    // 本地存在登录标记且已标记为登录状态时,拉取最新用户信息
    if (userId && isLoggedIn) {
      axios.get(`/user/${userId}`)
        .then(res => {
          dispatch(authActions.setUser(res.data.user))
        })
        .catch(err => {
          // 拉取失败说明登录态已失效,清理缓存重置状态
          localStorage.removeItem('userId')
          dispatch(authActions.logout())
        })
    }
  }, [dispatch, isLoggedIn])

  // 原有路由、页面渲染逻辑保持不变
}

4. 组件渲染增加判空防御

所有渲染用户信息的位置,使用可选链?.做判空,或者增加加载状态判断,避免数据未返回时读取属性报错,例如:

// 错误写法:user为null时直接读name会报错
<div>{user.name}</div>

// 正确写法:可选链判空,数据不存在时暂时不渲染或显示占位
<div>{user?.name}</div>

注意事项
  • 不要在Promise的.catch回调中只打印错误不做分支处理,错误场景要返回明确的边界值,避免后续逻辑拿到undefined触发连锁报错。
  • 涉及异步拉取的数据,渲染时必须做判空处理,不要假设数据一定存在。
  • 持久化登录态除了存用户ID,建议后续结合token做鉴权,接口请求时携带token做身份校验,避免接口被恶意调用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 08:15:34