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

循环内嵌套Axios调用首次渲染不展示数据问题排查

问题描述

组件会发起三次不同的Axios请求,其中getProductsByIds和getLegalNamesById这两次请求在getAllSearchResults请求返回结果的循环内部调用。组件首次渲染完成后仅展示getAllSearchResults请求的数据,另外两个请求的数据需在与组件交互(如点击)后才会渲染,不清楚问题出在哪里?

代码实现如下:

let panels = {documentSource: {summary: 'Document Source', content: new Set()},
  legalName: {summary: 'Legal Name', content: new Set()},
  productName: {summary: 'Product Name', content: new Set()},};

const RenderData = () => {

  const [accordionResults, setAccordionResults] = useState(null);
  const [isLoadingLegalName, setIsLoadingLegalName] = useState(true);
  const [isLoadingProductName, setIsLoadingProductName] = useState(true);

  useEffect(() => {
      fetchResults();
  }, []);
  
  const fetchResults = () => {
    return searchService()
    .getAllSearchResults("search", 650)
    .then((response) => {
      let results = response.data.results;
      if (results.length > 0) {
        Promise.all(results.map((obj, index) => {
          panels.documentSource.content.add(obj.A.value[0]);
          getLegalName(obj.B.value[0]);
          getProductName(obj.C.value[0]);
        }))
      }
      setAccordionResults(panels);
    }).catch((error) => {
      console.log(error);
    });
  };

  const getLegalName = (id) => {
    return CommonRefService()
    .getLegalNamesById([id])
    .then((response) => {
      const legalName = response.data.results[0];
      panels.legalName.content.add(legalName.name + ' - ' + legalName.id);
      setIsLoadingLegalName(false);
    }).catch((error) => {
      console.log(error);
    });
  };

  const getProductName = (id) => {
    return commonService()
    .getProductsByIds([id])
    .then((response) => {
      const productName = response.data[0].prodType;
      panels.productName.content.add(productName);
      setIsLoadingProductName(false);
    }).catch((error) => {
      console.log(error);
    });
  };
  
  if(isLoadingProductName || isLoadingLegalName) {
    return <div>Loading...</div>;
  }
}
问题分析与修复

核心问题点

  1. 直接修改外部非状态变量:panels是组件外部的普通对象,直接修改它的content集合不会触发React组件的重新渲染。React只有在状态(useState/useReducer)更新时才会重新渲染组件,所以即使后续请求拿到数据,组件也不会自动更新视图,只有交互触发重新渲染后才会显示新数据。
  2. Promise.all未正确收集异步请求:当前Promise.all里的map返回的是undefined(因为内部没有返回getLegalName和getProductName的Promise),导致setAccordionResults会在getAllSearchResults完成后立即执行,此时后两个请求还没返回数据,panels里的对应内容是空的。
  3. Loading状态管理错误:多个请求场景下,只要第一个getLegalName或getProductName完成就把对应的loading设为false,但其他请求可能还在进行,会导致loading提前结束但数据未完全加载。
  4. 状态更新方式错误:即使panels是状态,直接修改Set的内容(add方法)属于修改引用类型的内部值,React无法检测到变化,不会触发重新渲染。

修复后的代码

const RenderData = () => {
  // 初始化状态为包含空Set的结构,替代外部的panels变量
  const [accordionResults, setAccordionResults] = useState({
    documentSource: { summary: 'Document Source', content: new Set() },
    legalName: { summary: 'Legal Name', content: new Set() },
    productName: { summary: 'Product Name', content: new Set() },
  });
  // 用请求计数来管理loading,避免单个请求完成就关闭loading
  const [pendingRequests, setPendingRequests] = useState(0);

  useEffect(() => {
    fetchResults();
  }, []);

  const fetchResults = () => {
    searchService()
      .getAllSearchResults("search", 650)
      .then((response) => {
        const results = response.data.results;
        if (results.length === 0) {
          setAccordionResults(prev => ({...prev}));
          return;
        }

        // 计算需要发起的总请求数(每个result对应2个请求)
        setPendingRequests(results.length * 2);
        
        // 收集所有异步请求的Promise
        const allRequests = results.flatMap((obj) => {
          // 先更新documentSource数据,创建新的Set避免直接修改状态
          setAccordionResults(prev => ({
            ...prev,
            documentSource: {
              ...prev.documentSource,
              content: new Set(prev.documentSource.content).add(obj.A.value[0])
            }
          }));

          // 返回两个请求的Promise
          return [
            getLegalName(obj.B.value[0]),
            getProductName(obj.C.value[0])
          ];
        });

        // 等待所有请求完成
        Promise.all(allRequests).then(() => {
          // 所有请求完成,可做后续处理
        }).catch(err => console.log(err));
      })
      .catch((error) => {
        console.log(error);
        setPendingRequests(0);
      });
  };

  const getLegalName = (id) => {
    return CommonRefService()
      .getLegalNamesById([id])
      .then((response) => {
        const legalName = response.data.results[0];
        const newLegalName = `${legalName.name} - ${legalName.id}`;
        // 创建新的Set并更新状态,触发重新渲染
        setAccordionResults(prev => ({
          ...prev,
          legalName: {
            ...prev.legalName,
            content: new Set(prev.legalName.content).add(newLegalName)
          }
        }));
        // 完成一个请求,计数减1
        setPendingRequests(prev => prev - 1);
      })
      .catch((error) => {
        console.log(error);
        setPendingRequests(prev => prev - 1);
      });
  };

  const getProductName = (id) => {
    return commonService()
      .getProductsByIds([id])
      .then((response) => {
        const productName = response.data[0].prodType;
        // 创建新的Set并更新状态,触发重新渲染
        setAccordionResults(prev => ({
          ...prev,
          productName: {
            ...prev.productName,
            content: new Set(prev.productName.content).add(productName)
          }
        }));
        // 完成一个请求,计数减1
        setPendingRequests(prev => prev - 1);
      })
      .catch((error) => {
        console.log(error);
        setPendingRequests(prev => prev - 1);
      });
  };

  // 当还有未完成的请求时显示loading
  if (pendingRequests > 0) {
    return <div>Loading...</div>;
  }

  // 这里补充你的渲染逻辑,比如渲染accordionResults
  return (
    <div>
      {/* 示例渲染逻辑 */}
      {Object.values(accordionResults).map(panel => (
        <div key={panel.summary}>
          <h3>{panel.summary}</h3>
          <ul>
            {Array.from(panel.content).map(item => (
              <li key={item}>{item}</li>
            ))}
          </ul>
        </div>
      ))}
    </div>
  );
};

关键修改说明

  • 将panels改为组件内部的状态,所有数据更新通过setAccordionResults完成,确保触发重新渲染。
  • 使用pendingRequests计数管理loading状态,只有所有请求完成后才隐藏loading。
  • 更新Set时创建新的Set实例(new Set(prev.content).add(item)),避免直接修改原状态的引用类型,确保React能检测到状态变化。
  • 正确收集所有异步请求的Promise到allRequests数组,用Promise.all等待所有请求完成。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 11:38:09