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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 15:03:48