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

自定义useFetch Hook的error状态与使用组件不同步问题

问题分析与解决

你的问题核心在于闭包捕获旧状态和React setState的异步特性,导致调用sendRequest后无法获取最新的error值,以下是具体原因和解决方案:


问题根源

1. 闭包锁定旧状态

deleteContactHandler使用useCallback但依赖项为空数组,这会让它一直引用组件首次渲染时的error(初始值为null)。哪怕useFetch里通过setError更新了状态,回调里的error还是旧的闭包值,自然拿不到最新错误。

2. setState的异步更新特性

sendRequest里的setError是异步执行的,你在await sendRequest后立刻访问error,此时状态还未完成更新,拿到的依然是之前的值。

另外还有个小bug:原useFetch里的const data = response.json();漏加了await,会返回Promise而非解析后的实际数据。


解决方案

方案一:让sendRequest抛出错误,组件内直接捕获(推荐)

修改useFetch,在catch块中抛出错误,让组件调用时通过try/catch直接获取错误,无需依赖Hook返回的error状态:

const useFetch = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const sendRequest = async (
    url: string,
    requestConfig?: RequestInit | undefined
  ) => {
    setIsLoading(true);
    setError(null);

    try {
      const response = await fetch(url, {
        method: requestConfig?.method || "GET",
        headers: requestConfig?.headers || {},
        body: requestConfig?.body ? JSON.stringify(requestConfig.body) : null,
      });

      if (!response.ok) throw new Error("Request failed!");

      const data = await response.json(); // 补全await,获取实际数据
      return data;
    } catch (error: any) {
      const errorMsg = error.message;
      setError(errorMsg);
      throw new Error(errorMsg); // 抛出错误,让组件直接捕获
    } finally {
      setIsLoading(false);
    }
  };

  return {
    isLoading,
    error,
    sendRequest,
  };
};

然后修改组件的deleteContactHandler:

const deleteContactHandler = useCallback(async () => {
  try {
    await sendRequest(`${BASE_API_URL}/users/${contact.id}`, {
      method: "DELETE",
    });
    // 请求成功,更新联系人列表
    setContacts((prevContacts) => 
      prevContacts.filter((item) => item.id !== contact.id)
    );
  } catch (err: any) {
    console.log("删除失败:", err.message); // 直接拿到错误信息
  }
}, [sendRequest, contact.id, setContacts]); // 补全所有依赖项

方案二:修复闭包问题,监听error状态变化

如果坚持依赖Hook返回的error状态,可以补全useCallback依赖项,并用useEffect监听error变化处理后续逻辑:

function Contact({ contact, setContacts }: ContactProps) {
  const { isLoading, error, sendRequest } = useFetch();
  const [isDeleteTriggered, setIsDeleteTriggered] = useState(false);

  const deleteContactHandler = useCallback(async () => {
    setIsDeleteTriggered(true);
    await sendRequest(`${BASE_API_URL}/users/${contact.id}`, {
      method: "DELETE",
    });
  }, [sendRequest, contact.id]);

  // 监听删除触发状态和error,处理列表更新
  useEffect(() => {
    if (isDeleteTriggered && error === null) {
      setContacts((prevContacts) => 
        prevContacts.filter((item) => item.id !== contact.id)
      );
    }
    // 重置触发标记
    if (isDeleteTriggered) setIsDeleteTriggered(false);
  }, [isDeleteTriggered, error, contact.id, setContacts]);

  // 其余组件代码不变
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 01:48:11