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

