React首次调用用户API报401未授权错误 刷新页面后正常
React项目首次调用用户API返回401未授权、刷新后正常的问题排查与修复
问题现象
每次调用用户API时首次请求返回unauthorized-401未授权错误,刷新页面后接口即可正常响应。
相关代码片段
1. 组件内拉取用户信息实现
import { useContext, useEffect, useState, useCallback } from "react"; import jwtDecode from "jwt-decode"; import AuthContext from "../../store/auth-context"; import { getUserInfo } from "../../lib/api"; const Products = () => { const authCtx = useContext(AuthContext); const decodedToken = jwtDecode(authCtx.contextValue.token); const userData = decodedToken.data.userData; const fetchUserInfo = useCallback(async () => { setIsLoading(true); getUserInfo(userData.id).then((response) => { let groupId = response.data[0].work_group; setIsLoading(false); }); }, []); useEffect(() => { fetchUserInfo(); }, []);
2. 用户API请求封装代码
export async function getUserInfo(userId) { let token = localStorage.getItem('token'); const response = await fetch(`${CODE_CORP_API}/users/${userId}`, { method: "GET", headers: { "Content-Type": "application/json", "Authorization" : `Bearer ${token}` }, }); const data = await response.json(); if (!response.ok) { throw new Error(data.message || "Could not create account."); } return data; }
问题产生原因
核心是token写入localStorage的时机晚于首次接口请求触发时机,属于React初始化渲染阶段的典型时序问题:
- 页面首次加载时,
AuthContext的初始化是异步流程:要么是登录后异步将token存入localStorage,要么是Provider挂载后才从本地存储读取token。组件首次挂载时useEffect会立刻触发fetchUserInfo,此时token还未成功写入/读取到有效状态 - 此时
getUserInfo从localStorage取到的token值为null,请求头里携带的是无效的Bearer null,服务端鉴权不通过返回401 - 手动刷新页面时,token早已持久化在localStorage中,请求能正常携带合法token,因此接口响应正常
除此之外现有代码还存在三个隐性问题会放大异常:
useCallback依赖数组为空,闭包会永久缓存首次渲染时的userData值,后续token更新后回调内拿到的还是初始旧值- 组件直接在渲染阶段执行
jwtDecode(authCtx.contextValue.token),首次渲染token为空时会直接抛出解码错误,导致页面白屏 - 请求封装层没有做token空值校验,无效请求会直接发送到服务端
修复方案
第一步:调整组件侧逻辑,等token就绪后再发请求
const Products = () => { const authCtx = useContext(AuthContext); const [isLoading, setIsLoading] = useState(false); // 先判断token是否存在,不存在时不执行解码 const userData = authCtx.contextValue.token ? jwtDecode(authCtx.contextValue.token).data.userData : null; const fetchUserInfo = useCallback(async () => { // 没有用户ID时直接返回,不发无效请求 if (!userData?.id) return; setIsLoading(true); try { const response = await getUserInfo(userData.id); const groupId = response.data[0].work_group; // 后续groupId业务处理逻辑 } catch (err) { console.error('获取用户信息失败', err); } finally { setIsLoading(false); } // 把userData加入依赖数组,值更新时重新生成回调 }, [userData?.id]); useEffect(() => { fetchUserInfo(); // 依赖fetchUserInfo,token就绪、userData更新后会重新执行请求 }, [fetchUserInfo]); // token未加载完成时展示身份验证态 if (!userData) return <div>验证身份中...</div>; return ( // 原有组件渲染内容 ) }
第二步:优化请求封装逻辑,增加空token拦截
export async function getUserInfo(userId) { const token = localStorage.getItem('token'); // token为空时直接抛出错误,不发起无效请求 if (!token) { throw new Error("登录凭证已失效,请重新登录"); } const response = await fetch(`${CODE_CORP_API}/users/${userId}`, { method: "GET", headers: { "Content-Type": "application/json", "Authorization" : `Bearer ${token}` }, }); const data = await response.json(); if (!response.ok) { // 区分401场景,方便全局做登录跳转逻辑 if (response.status === 401) { throw new Error("登录已过期,请重新登录"); } throw new Error(data.message || "获取用户信息失败"); } return data; }
第三步(可选):优化AuthContext初始化逻辑
在AuthContext Provider里增加全局初始化状态,应用挂载时先同步读取localStorage里的token,完成初始化后再渲染子路由,从根源避免子组件在token未就绪时发请求:
// auth-context.js 示例 const AuthContextProvider = ({ children }) => { const [contextValue, setContextValue] = useState({ token: null, isReady: false // 增加初始化完成标记 }); useEffect(() => { const storedToken = localStorage.getItem('token'); setContextValue({ token: storedToken, isReady: true }) }, []) // 未初始化完成时展示全局加载态 if (!contextValue.isReady) { return <div>加载中...</div> } return ( <AuthContext.Provider value={{contextValue, setContextValue}}> {children} </AuthContext.Provider> ) }
内容的提问来源于stack exchange,提问作者Srini
相关产品推荐
相关产品推荐

