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

涉及代码如下:
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
相关产品推荐
相关产品推荐

