自定义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
相关产品推荐
相关产品推荐

