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

React useEffect获取数据渲染异常求助:仅状态变更时显示内容

问题排查与修复方案

核心问题分析

你的代码存在多个导致数据无法正常渲染的问题,逐一拆解如下:

  • 重复挂载useEffect的覆盖问题
    你写了两个依赖为空数组的useEffect,组件挂载时会同时执行。第二个useEffect里直接执行setCampaigns(stateCampaigns),而stateCampaigns初始是空数组,这会直接覆盖第一个useEffect中异步请求刚设置的campaigns值,导致数据被清空。同时campaigns.length > 0 && setShowCampaigns(true)的判断也不成立,因为此时campaigns还是初始空数组,所以showCampaigns一直是false,渲染逻辑不会触发。

  • 状态更新的异步性导致循环无效
    第一个useEffect中,执行setCampaigns(res.data.campaigns)后立刻调用campaigns.forEach,但React的状态更新是异步的,此时campaigns仍然是初始的空数组,所以这个循环根本不会执行,也就不会去请求每个campaign的leads数据。

  • 异步请求的批量处理逻辑错误
    使用forEach发起异步请求时,每个请求的finally都会调用setCampaigns(resS),导致状态被多次更新;同时leads.push(leadsRes[i])中的leadsRes是当前campaign的所有leads,取索引i完全不合理,id === leadsRes[i]?.campaignId的判断逻辑也不严谨,容易导致resS收集不到正确数据。

  • 状态冗余与逻辑混乱
    stateCampaigns和campaigns两个状态重复,且赋值逻辑混乱,没有明确区分原始数据和过滤后数据的职责,导致数据流转出错。


修复后的完整代码

import { useDispatch, useSelector } from "react-redux";
import _ from "lodash";
import styles from "../../../styles/CreateLeads.module.css";
import { getAgentCampaigns, getCampaignLeads } from "../../../utils/requests/campaign";
import { hideTopLoader, showTopLoader } from "../../../store/actions/TopLoader/topLoaderActions";
import CampaignSection from "./CampaignSection";
import Empty from "../Empty/Empty";
import SectionHeader from "../SectionHeader/SectionHeader";
import SearchBar from "../SearchBar/SearchBar";
import { RootState } from "../../../store/store";

const CreateLeadsCardsWrapper: React.FC = () => {
  const authToken = useSelector((store: any) => store.authenticationReducer.authToken);
  // 合并冗余状态,originalCampaigns存原始数据,filteredCampaigns存过滤后的数据
  const [originalCampaigns, setOriginalCampaigns] = React.useState<any[]>([]);
  const [filteredCampaigns, setFilteredCampaigns] = React.useState<any[]>([]);
  const [showCampaigns, setShowCampaigns] = React.useState<boolean>(false);
  const [filter, setFilter] = React.useState<string>("");

  const dispatch = useDispatch();

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const campaignSearch = e.target.value.trim();
    setFilter(campaignSearch);

    if (campaignSearch.length === 0) {
      // 清空搜索时显示全部数据
      setFilteredCampaigns(originalCampaigns);
      return;
    }

    const searchValues = [
      campaignSearch,
      campaignSearch.toLowerCase(),
      campaignSearch.toUpperCase(),
      campaignSearch.charAt(0).toUpperCase() + campaignSearch.slice(1)
    ];

    const results = originalCampaigns.filter(({ leads }) => {
      if (!leads) return false;
      return leads.some(lead => {
        return searchValues.some(val => 
          lead.firstName.includes(val) ||
          lead.lastName.includes(val) ||
          lead.email.includes(val) ||
          lead.phoneNo.includes(val)
        );
      });
    });

    setFilteredCampaigns(results);
  };

  React.useEffect(() => {
    const fetchData = async () => {
      dispatch(showTopLoader());
      try {
        // 1. 获取基础campaign数据
        const campaignsRes = await getAgentCampaigns(authToken, "accepted");
        const rawCampaigns = campaignsRes.data.campaigns;

        if (!rawCampaigns.length) {
          setOriginalCampaigns([]);
          setFilteredCampaigns([]);
          setShowCampaigns(true);
          return;
        }

        // 2. 批量请求每个campaign的leads,确保所有请求完成后再更新状态
        const campaignsWithLeads = await Promise.all(
          rawCampaigns.map(async (campaign) => {
            try {
              const leadsRes = await getCampaignLeads(authToken, campaign.Campaign.id);
              return {
                ...campaign,
                leads: leadsRes.data.campaignLeads
              };
            } catch (error) {
              // 请求失败时返回原campaign数据,避免整个流程中断
              return { ...campaign, leads: [] };
            }
          })
        );

        // 3. 更新状态,触发渲染
        setOriginalCampaigns(campaignsWithLeads);
        setFilteredCampaigns(campaignsWithLeads);
        setShowCampaigns(true);
      } catch (error) {
        console.error("加载数据失败:", error);
        setOriginalCampaigns([]);
        setFilteredCampaigns([]);
        setShowCampaigns(true);
      } finally {
        dispatch(hideTopLoader());
      }
    };

    fetchData();
  }, [authToken, dispatch]); // 添加依赖,避免闭包问题

  return (
    <div className={styles.wrappers}>
      <SectionHeader text="Campaign Leads" />
      {showCampaigns && originalCampaigns.length === 0 && (
        <>
          <Empty description="No agents yet" />
          <p>Join a campaign.</p>
        </>
      )}
      {showCampaigns && originalCampaigns.length > 0 && (
        <>
          <p className="text-grey-500">Create Leads for Campaigns.</p>
          <section className={styles.container}>
            <SearchBar
              placeholder="Find Campaign Leads"
              onChange={handleChange}
              value={filter} // 绑定搜索值,避免输入框状态不一致
            />
            {filteredCampaigns.map((item) => (
              <CampaignSection
                key={item.Campaign.id}
                id={item.Campaign.id}
                name={item.Campaign.name}
                imageUrl={item.Campaign.iconUrl}
                campaignType={item.Campaign.type}
                productType={item.Campaign.Products[0]?.type}
              />
            ))}
          </section>
        </>
      )}
    </div>
  );
};

export default CreateLeadsCardsWrapper;

关键修复点说明

  1. 合并冗余状态:用originalCampaigns存储原始加载的完整数据,filteredCampaigns存储搜索过滤后的数据,职责清晰,避免状态混乱。
  2. 修正异步请求时序:使用Promise.all批量处理所有campaign的leads请求,确保所有数据加载完成后一次性更新状态,避免多次触发渲染。
  3. 移除冲突的useEffect:删除第二个挂载时的useEffect,将showCampaigns的设置放在数据加载完成后,确保数据就绪后才触发渲染逻辑。
  4. 修复搜索逻辑:简化搜索值的生成,用searchValues数组统一处理各种大小写情况,同时绑定搜索框的value属性,避免输入状态不一致。
  5. 添加正确的依赖项:给useEffect添加authToken和dispatch作为依赖,避免闭包导致的旧值问题(React 18+中dispatch可以不用加,但加上更严谨)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 16:06:01