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

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.

错误原因分析

  1. 初始loading状态错误:Hook中loading初始值设为false,但组件挂载时会立即触发API请求(因为传入了初始url),而useEffect是在组件首次渲染完成后才执行。这导致组件第一次渲染时,loading还是false,直接跳过加载状态判断,尝试调用data.map,但此时data仍为初始的null,从而抛出错误。
  2. 重复设置loading状态:try块内和finally块都调用了setLoading(false),虽然不会导致报错,但属于冗余操作,可能引发不必要的状态更新。
  3. 依赖项处理不严谨: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 05:44:52