React自定义useGetApi Hook数据更新异常问题求助
React自定义Hook数据加载问题及组件报错修复
问题概述
我是React新手,写了一个useGetApi自定义Hook用于从API获取数据,但遇到两个问题:
- 即使
loading状态变为false,data仍保持初始null状态,疑似更新延迟 - 使用该Hook的
PostList组件首次渲染时报错:Uncaught TypeError: Cannot read properties of null (reading 'map')
自定义Hook useGetApi代码
import { useState, useEffect } from 'react'; import useAxiosInstance from './useAxiosInstance'; const useGetApi = (initialurl = null, initialparams = {}) => { const [data, setData] = useState(null); const [error, setError] = useState(null); const [url, setUrl] = useState(initialurl); const [params, setParams] = useState(initialparams); const [loading, setLoading] = useState(false); // 初始状态为false const [refreshKey, setRefreshKey] = useState(true); const axiosInstance = useAxiosInstance(); useEffect(() => { if (!url) return; const fetchData = async () => { setLoading(true); try { const response = await axiosInstance.get(url, {params}); setData(response.data); setLoading(false); // 重复设置loading } catch (err) { setError({ message: err.response?.data?.message || err.message || 'An unexpected error occurred', details: err.response?.data || null, }); } finally { setLoading(false); } }; fetchData(); }, [url, JSON.stringify(params), refreshKey]); const triggerRefresh = () => setRefreshKey(prev=>!prev); return { data, error, loading, setUrl, setParams, triggerRefresh }; }; export default useGetApi;
PostList组件代码
import React from 'react'; import useGetApi from '../hooks/requestMethods/useGetApi'; const PostList = () => { const { data, error, loading, triggerRefresh } = useGetApi('https://jsonplaceholder.typicode.com/posts'); if (loading) { return <p>Loading posts...</p>; } if (error) { return <p>Error fetching posts: {error.message}</p>; } return ( <div> <h1>Posts</h1> <button onClick={triggerRefresh} disabled={loading}> {loading ? 'Refreshing...' : 'Refresh Posts'} </button> <ul> { data.map(post => ( <li key={post.id}> <h2>{post.title}</h2> <p>{post.body}</p> </li> ))} </ul> </div> ); }; export default PostList;
报错信息
PostList.jsx:26 Uncaught TypeError: Cannot read properties of null (reading 'map') at PostList (PostList.jsx:26:16) @react-refresh:247 The above error occurred in the <PostList> component: at PostList (http://localhost:5175/src/pages/PostList.jsx?t=1735830812705:22:52) at RenderedRoute (http://localhost:5175/node_modules/.vite/deps/react-router-dom.js?v=8d379189:4069:5) at Routes (http://localhost:5175/node_modules/.vite/deps/react-router-dom.js?v=8d379189:4539:5) at div at Router (http://localhost:5175/node_modules/.vite/deps/react-router-dom.js?v=8d379189:4482:15) at BrowserRouter (http://localhost:5175/node_modules/.vite/deps/react-router-dom.js?v=8d379189:5228:5) at Provider (http://localhost:5175/node_modules/.vite/deps/chunk-6DOCI2SE.js?v=8d379189:1097:3) at App Consider adding an error boundary to your tree to customize error handling behavior.
错误原因分析
- 初始loading状态错误:Hook中
loading初始值设为false,但组件挂载时会立即触发API请求(因为传入了初始url),而useEffect是在组件首次渲染完成后才执行。这导致组件第一次渲染时,loading还是false,直接跳过加载状态判断,尝试调用data.map,但此时data仍为初始的null,从而抛出错误。 - 重复设置loading状态:
try块内和finally块都调用了setLoading(false),虽然不会导致报错,但属于冗余操作,可能引发不必要的状态更新。 - 依赖项处理不严谨:
useEffect依赖项使用JSON.stringify(params),虽然能解决对象引用变化的问题,但每次渲染都会生成新的字符串,可能导致useEffect不必要重复执行。
修复方案
1. 修正初始loading状态
将Hook中loading的初始值改为!!initialurl,即当传入初始url时,默认进入加载状态,确保组件首次渲染时显示加载提示。
2. 移除重复的loading状态设置
只保留finally块中的setLoading(false),确保无论请求成功或失败,都会正确结束加载状态。
3. 补充状态重置逻辑
请求成功时清空错误状态,请求失败时清空数据状态,避免状态残留导致的异常渲染。
修复后的useGetApi代码
import { useState, useEffect } from 'react'; import useAxiosInstance from './useAxiosInstance'; const useGetApi = (initialurl = null, initialparams = {}) => { const [data, setData] = useState(null); const [error, setError] = useState(null); const [url, setUrl] = useState(initialurl); const [params, setParams] = useState(initialparams); const [loading, setLoading] = useState(!!initialurl); // 初始url存在时自动进入加载状态 const [refreshKey, setRefreshKey] = useState(true); const axiosInstance = useAxiosInstance(); useEffect(() => { if (!url) { setLoading(false); return; } const fetchData = async () => { setLoading(true); try { const response = await axiosInstance.get(url, {params}); setData(response.data); setError(null); // 请求成功清空错误 } catch (err) { setError({ message: err.response?.data?.message || err.message || 'An unexpected error occurred', details: err.response?.data || null, }); setData(null); // 请求失败清空数据 } finally { setLoading(false); } }; fetchData(); }, [url, JSON.stringify(params), refreshKey, axiosInstance]); const triggerRefresh = () => setRefreshKey(prev=>!prev); return { data, error, loading, setUrl, setParams, triggerRefresh }; }; export default useGetApi;
额外优化建议
在PostList组件中,可以额外增加data存在性判断,作为双重保障:
// 在error判断后增加 if (!data) { return <p>No posts found</p>; }
这样即使Hook状态出现异常,也不会触发map报错。
内容的提问来源于stack exchange,提问作者Gate Study
相关产品推荐
相关产品推荐

