合并两个API数据后React组件无法渲染的问题求解
问题:接口数据合并后无法触发组件渲染,添加依赖引发无限循环
请求两个接口并将数据存入state后,无法在状态变更时渲染组件。若将相关状态变量加入useEffect的依赖数组,会引发无限渲染。目前仅能通过将合并后的状态加入依赖数组实现组件加载后的渲染,但这并非合理方案。
组件代码如下:
import { ChangeEvent, FC, useEffect, useState } from "react"; import spacex from "../api/spacex"; import CardGrid from "../components/CardGrid"; import Header from "../components/Header"; import Pagination from "../components/Pagination"; import SkeletonGrid from "../components/SkeletonGrid"; type Launch = { mission_name: string; }; const LaunchesMain: FC = () => { const [launches, setLaunches] = useState<any>([]); const [rockets, setRockets] = useState<any>([]); const [merged, setMerged] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [postsPerPage, setPostsPerPage] = useState(9); const [searchTerm, setSearchTerm] = useState(""); const [filteredResult, setFilteredResult] = useState([]); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const fetchRockets = async () => { const responseRocket = await spacex.get("/rockets"); const responseLaunches = await spacex.get("/launches"); setRockets(responseRocket.data); setLaunches(responseLaunches.data); }; fetchRockets().then(() => { const mergedApis = () => { const launchesCopy: any = [...launches]; for (let i = 0; i < launches.length; i++) { for (let j = 0; j < rockets.length; j++) { if (launches[i].rocket.rocket_name === rockets[j].rocket_name) { launchesCopy[i].rocket = rockets[j]; } } } setMerged(launchesCopy); setIsLoading(false); }; mergedApis(); }); }, []); console.log(merged); const handleSearchChange = (event: ChangeEvent<HTMLInputElement>) => { setSearchTerm(event.target.value); if (searchTerm.length === 0) { setFilteredResult(merged); } else if (searchTerm.length > 0) { const filteredData = merged.filter((launch: Launch) => { return `${launch.mission_name}` .toLowerCase() .includes(searchTerm.toLowerCase()); }); setFilteredResult(filteredData); } }; const lastPostIndex = currentPage * postsPerPage; const firstPostIndex = lastPostIndex - postsPerPage; const currentPosts = merged.slice(firstPostIndex, lastPostIndex); return ( <> <Header /> <div className="text-white"> <div> <input style={{ background: "linear-gradient(0deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05)), #121212", }} onChange={(event) => handleSearchChange(event)} placeholder="Search all launches..." value={searchTerm} className="md:w-[26rem] w-[16rem] h-[3rem] rounded-lg mt-10 mx-5 md:mx-24 rounded-3" /> </div> <div className="mx-5 md:ml-24 mt-5 opacity-40"> Total({currentPosts.length}) </div> {isLoading ? ( <SkeletonGrid cards={postsPerPage} /> ) : ( <CardGrid postsData={currentPosts} filteredResult={filteredResult} searchTerm={searchTerm} /> )} <Pagination totalPosts={merged.length} postsPerPage={postsPerPage} setCurrentPage={setCurrentPage} currentPage={currentPage} /> </div> </> ); }; export default LaunchesMain;
核心问题分析
代码中存在两个关键问题:
fetchRockets的then回调里调用mergedApis时,直接读取了组件当前的launches和rockets状态——但React的setState是异步操作,此时这两个状态还未被接口返回的数据更新,合并逻辑拿到的是空数组,导致merged始终为空,无法触发渲染。- 若将
launches/rockets加入useEffect依赖数组,每次状态更新都会重新执行请求和合并逻辑,再次触发状态更新,形成无限循环。
解决方案
1. 直接用接口返回数据完成合并,跳过中间状态
异步请求完成后,直接使用接口返回的原始数据做合并,不需要先存入组件状态再读取,彻底避免异步状态更新的延迟问题。
修改后的useEffect代码:
useEffect(() => { const fetchAndMergeData = async () => { try { setIsLoading(true); // 并行请求两个接口,提升性能 const [responseRocket, responseLaunches] = await Promise.all([ spacex.get("/rockets"), spacex.get("/launches") ]); const rocketsData = responseRocket.data; const launchesData = responseLaunches.data; // 用map+find替代嵌套循环,更高效清晰 const mergedData = launchesData.map(launch => { const matchedRocket = rocketsData.find(rocket => launch.rocket.rocket_name === rocket.rocket_name ); // 找不到匹配时保留原rocket数据,避免报错 return {...launch, rocket: matchedRocket || launch.rocket}; }); setMerged(mergedData); setIsLoading(false); } catch (error) { console.error("数据请求/合并失败:", error); setIsLoading(false); } }; fetchAndMergeData(); }, []);
2. 移除不必要的状态变量
既然不需要单独存储launches和rockets状态,直接删除对应的useState声明:
// 删除这两行代码 const [launches, setLaunches] = useState<any>([]); const [rockets, setRockets] = useState<any>([]);
3. 修复搜索逻辑的异步状态问题
setSearchTerm是异步操作,直接在之后读取searchTerm会拿到旧值,导致搜索逻辑出错。改用event.target.value直接处理:
const handleSearchChange = (event: ChangeEvent<HTMLInputElement>) => { const inputValue = event.target.value.toLowerCase(); setSearchTerm(inputValue); if (inputValue.length === 0) { setFilteredResult(merged); } else { const filteredData = merged.filter((launch: Launch) => launch.mission_name.toLowerCase().includes(inputValue) ); setFilteredResult(filteredData); } };
4. 优化分页与统计逻辑(可选)
让分页和统计数据根据搜索状态动态切换,使用过滤后的数据或原始合并数据:
// 动态选择要展示的数据 const displayData = searchTerm ? filteredResult : merged; const lastPostIndex = currentPage * postsPerPage; const firstPostIndex = lastPostIndex - postsPerPage; const currentPosts = displayData.slice(firstPostIndex, lastPostIndex);
同时更新统计文本:
<div className="mx-5 md:ml-24 mt-5 opacity-40"> Total({displayData.length}) </div>
方案生效原因
- 直接使用接口返回的数据合并,确保合并逻辑拿到完整的接口数据,不会因React异步状态更新导致读取旧值。
- useEffect依赖数组保持为空,仅在组件挂载时执行一次请求和合并,不会引发无限循环。
- 修复搜索逻辑的异步状态问题,保证搜索结果的正确性。
内容的提问来源于stack exchange,提问作者grunhaus
相关产品推荐
相关产品推荐

