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

React中useState存储对象数组返回空数组及渲染报错问题求助

React自定义Hook中useState更新与渲染报错问题

问题场景

  • NodeJS服务器在localhost:7000返回格式正确的营养数据:
    {"carbs":18,"fats":0, "kcal":184,"productName":"Jenever bessen-","protein":0, "sugars":18}, ...
    
  • request.js中获取数据的函数正常工作:
    const API_URL = 'http://localhost:7000';
    
    // Load planets and return as JSON.
    async function httpGetNutrition() {
      const response = await fetch(`${API_URL}/nutrition`); // works 
      console.log('fetched data')
      return await response.json(); // works
    }
    
    export {
      httpGetNutrition,
    };
    

遇到的两个核心问题

问题1:useState更新后状态未即时同步

在自定义Hook UseNutrition 中,遍历获取到的数据调用setNutrition后,console.log(nutrition)始终返回空数组,后续判断nutrition.length也为0:

import { useCallback, useEffect, useState } from "react";
import { httpGetNutrition } from "./requests";

function UseNutrition()
{   
    const [nutrition, setNutrition] = useState([])
    const [loading, setLoading] = useState(true)
    
    const getNutrition = useCallback(async () => {
        setLoading(true)
        try 
        {
            const fetchedNutrition = await httpGetNutrition(); // works
            fetchedNutrition.map((item) => {
                console.log(item) // 能正确打印每个item
                setNutrition([...nutrition, item]); // 尝试添加item到数组
                console.log(nutrition) // 始终输出空数组
            })
            
            if (nutrition.length > 0) 
            {
                console.log('set nutrition succesfully')
                console.log(nutrition.length)
            }
            else
            {
                console.log('data is of length 0') // 总是进入这个分支
            }
        
        }
        catch (err)
        {
            setLoading(false)
            console.log(`error: ${err}`)
        }
    },[]);

    if (loading)
    {
        <div className="loading">
        <h1>loading...</h1>
        </div>
    }

    useEffect(() => {
        getNutrition()
    }, [getNutrition]);

    return nutrition
}

export default UseNutrition;

问题2:直接赋值触发React渲染报错

尝试以下两种方式时,出现"Objects are not valid as a React child"错误:

  • setNutrition([...nutrition, ...item])
  • 移除map直接赋值:setNutrition(fetchedNutrition)
    报错信息:
react-dom.development.js:14887 Uncaught Error: Objects are not valid as a React child (found: object with keys {carbs, fats, kcal, productName, protein, sugars}). If you meant to render a collection of children, use an array instead.
    at throwOnInvalidObjectType

解决方案

针对问题1:状态更新的异步性与依赖项缺失

  1. useState更新是异步的:setNutrition调用后,nutrition不会立刻更新,后续的console.log和长度判断拿到的都是旧值。
  2. useCallback依赖项缺失:getNutrition的依赖数组为空,导致内部的nutrition始终是初始空数组,每次setNutrition都基于空数组添加单个item,最终只会保留最后一个item。
  3. 优化实现:
    • 不要在map里多次调用setNutrition,直接一次性赋值整个数组(推荐),或用函数式更新确保基于最新状态:
      // 方式1:直接赋值完整数据
      setNutrition(fetchedNutrition);
      // 方式2:增量更新时用函数式更新
      setNutrition(prevNutrition => [...prevNutrition, ...fetchedNutrition]);
      
    • 修正getNutrition逻辑,用finally确保loading状态正确关闭:
      const getNutrition = useCallback(async () => {
          setLoading(true);
          try {
              const fetchedNutrition = await httpGetNutrition();
              setNutrition(fetchedNutrition);
          } catch (err) {
              console.log(`error: ${err}`);
          } finally {
              setLoading(false); // 无论成功失败都关闭loading
          }
      }, []);
      

针对问题2:React不能直接渲染对象数组

报错原因是你直接返回了对象数组作为组件内容,React无法渲染原始对象。需要修改自定义Hook的返回逻辑,并在使用Hook的组件中把对象转换成可渲染的JSX:

  1. 修改自定义Hook:同时返回状态和loading标识,让调用组件处理渲染:
    function UseNutrition() {
        const [nutrition, setNutrition] = useState([]);
        const [loading, setLoading] = useState(true);
        
        // ... getNutrition函数 ...
    
        useEffect(() => {
            getNutrition();
        }, [getNutrition]);
    
        return { nutrition, loading };
    }
    
  2. 在调用组件中渲染数据:遍历数组生成JSX元素:
    import UseNutrition from './UseNutrition';
    
    function NutritionList() {
        const { nutrition, loading } = UseNutrition();
    
        if (loading) {
            return <div className="loading"><h1>loading...</h1></div>;
        }
    
        return (
            <div className="nutrition-list">
                {nutrition.map((item) => (
                    <div key={item.productName} className="nutrition-item">
                        <h3>{item.productName}</h3>
                        <p>热量:{item.kcal}kcal</p>
                        <p>碳水:{item.carbs}g</p>
                        <p>糖分:{item.sugars}g</p>
                    </div>
                ))}
            </div>
        );
    }
    

额外优化点

  • 遍历元素时使用唯一标识作为key(如productName),避免用index作为key
  • 确保服务器返回的是数组格式,而非单个对象或其他格式

内容的提问来源于stack exchange,提问作者Yorick de Jong

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 07:30:52