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

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;

解决方案

问题根源拆解

  1. setState异步特性导致日志误导:自定义Hook里console.log("il", itemsList)打印的是当前的itemsList初始值(空数组),因为setItemsList是异步更新状态的,不会立即改变itemsList,这是React正常机制,不是bug。
  2. 组件useEffect依赖缺失:组件里的useEffect依赖是空数组,仅在挂载时执行一次,此时itemsList还是初始空数组,过滤后product为空。后续itemsList更新时,该useEffect不会重新执行,导致product一直为空。
  3. 未处理组件卸载后的请求:如果组件在请求完成前被卸载,未取消的请求完成后调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 17:57:07