React中API数据无法通过setState更新状态的问题求助
问题
调用API后,通过console.log能正常查看返回的数据,但执行setItemsList(data)后itemsList仍为空,组件无法接收到itemsList的值。查资料后猜测是异步API请求尚未完成时组件已被卸载,请求完成后调用setState时作用于已卸载的组件,但无法解决该问题。
自定义Hook代码
import { useEffect, useState } from "react"; export const useProductsList = () => { const [itemsList, setItemsList] = useState([]); useEffect(() => { const fetchData = async () => { await fetch("http://localhost:4000/items") .then((resp) => resp.json()) .then((data) => { setItemsList(data); console.log("il", itemsList); }) .catch((err) => console.log("Err occured", err)); }; fetchData(); }, []); return { itemsList, setItemsList, }; };
组件代码
import React, { useEffect, useMemo, useRef, useState } from "react"; import { useParams } from "react-router-dom"; import { useItemsList } from "./hooks/useItemsList"; // import ImageDisplay from "../components/item/ImageDisplay"; const Item = () => { const { itemsList } = useItemsList(); const { id } = useParams(); const [product, setProduct] = useState([]); console.log(itemsList); useEffect(() => { setProduct(() => itemsList?.filter((prod) => parseInt(prod.id) === parseInt(id)) ); console.log("PRODUCT", product); }, []); return ( <div style={{ display: "grid" }}> <div>{/* <ImageDisplay product={productArr[0]} /> */}</div> <div> <p>{product[0].title}</p> <p>{product[0].description}</p> <p>{product[0].rating} / 5</p> <hr></hr> <p>{product[0].price}</p> <p>{product[0].discount}</p> </div> </div> ); }; export default Item;
解决方案
问题根源拆解
- setState异步特性导致日志误导:自定义Hook里
console.log("il", itemsList)打印的是当前的itemsList初始值(空数组),因为setItemsList是异步更新状态的,不会立即改变itemsList,这是React正常机制,不是bug。 - 组件useEffect依赖缺失:组件里的
useEffect依赖是空数组,仅在挂载时执行一次,此时itemsList还是初始空数组,过滤后product为空。后续itemsList更新时,该useEffect不会重新执行,导致product一直为空。 - 未处理组件卸载后的请求:如果组件在请求完成前被卸载,未取消的请求完成后调用
setItemsList会触发React警告,甚至造成内存泄漏。
修复自定义Hook
添加请求取消逻辑,统一用async/await写法:
import { useEffect, useState } from "react"; export const useProductsList = () => { const [itemsList, setItemsList] = useState([]); useEffect(() => { const abortController = new AbortController(); const signal = abortController.signal; const fetchData = async () => { try { const resp = await fetch("http://localhost:4000/items", { signal }); const data = await resp.json(); setItemsList(data); } catch (err) { // 忽略请求取消的错误 if (err.name !== "AbortError") { console.log("请求出错", err); } } }; fetchData(); // 组件卸载时取消请求 return () => abortController.abort(); }, []); return { itemsList }; };
修复组件
两种方案选其一即可:
方案1:修正useEffect依赖
import React, { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import { useProductsList } from "./hooks/useItemsList"; const Item = () => { const { itemsList } = useProductsList(); const { id } = useParams(); const [product, setProduct] = useState([]); // 依赖itemsList和id,两者变化时重新过滤 useEffect(() => { if (!itemsList.length || !id) return; const filtered = itemsList.filter(prod => parseInt(prod.id) === parseInt(id)); setProduct(filtered); }, [itemsList, id]); // 渲染前判断product是否存在,避免报错 if (!product.length) return <div>加载中...</div>; return ( <div style={{ display: "grid" }}> <div></div> <div> <p>{product[0].title}</p> <p>{product[0].description}</p> <p>{product[0].rating} / 5</p> <hr /> <p>{product[0].price}</p> <p>{product[0].discount}</p> </div> </div> ); }; export default Item;
方案2:用useMemo直接计算product(更推荐)
无需额外product状态,直接通过useMemo推导结果:
import React, { useMemo } from "react"; import { useParams } from "react-router-dom"; import { useProductsList } from "./hooks/useItemsList"; const Item = () => { const { itemsList } = useProductsList(); const { id } = useParams(); // 缓存过滤结果,仅itemsList或id变化时重新计算 const product = useMemo(() => { if (!itemsList.length || !id) return null; return itemsList.find(prod => parseInt(prod.id) === parseInt(id)); }, [itemsList, id]); if (!product) return <div>加载中...</div>; return ( <div style={{ display: "grid" }}> <div></div> <div> <p>{product.title}</p> <p>{product.description}</p> <p>{product.rating} / 5</p> <hr /> <p>{product.price}</p> <p>{product.discount}</p> </div> </div> ); }; export default Item;
内容的提问来源于stack exchange,提问作者Pranjal
相关产品推荐
相关产品推荐

