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

React中useState钩子无法更新状态,recipes始终为空数组求助

问题分析与解决方案

你遇到的核心问题是:API请求能成功获取数据(try块内的console可输出预期结果),但组件内的recipes状态始终为空数组,外部无法读取到更新后的数据。

主要原因及解决方法

1. useEffect中的状态更新时机问题

在useEffect里,你先调用setFoodType(location.state.type),紧接着执行handleSearchSubmit()。但setFoodType是异步操作,此时foodType还未更新为location.state.type的值,导致handleSearchSubmit中使用的foodType仍是初始空字符串。即便当前请求不受type参数影响,这个逻辑也会导致后续搜索出错。

解决方法:
直接在handleSearchSubmit中使用传入的location.state.type,避免依赖异步更新的state:

useEffect(()=>{
    const currentFoodType = location.state.type;
    setFoodType(currentFoodType);
    // 直接使用当前type值执行请求
    handleSearchSubmit(currentFoodType);
},[location.state.type])

// 修改handleSearchSubmit,支持传入type参数
const handleSearchSubmit = async (foodTypeParam) => {
    setIsError(false)
    setIsLoading(true)
    try{
        let q = search
        // 优先使用传入的参数, fallback到state中的值
        let type = foodTypeParam || foodType;
        let response = await axios(`https://api.spoonacular.com/recipes/complexSearch?apiKey=5ca51407f1a649a2b35aef72a28100b6&type=${type}&query=${q}`)
        console.log(response.data)
        if(response.data.recipes){
            console.log(response.data.recipes)
            setRecipes(response.data.recipes)
            setIsLoading(false)
        } else {
            setIsError(true)
            setIsLoading(false)
        }
    }
    catch(err){
        setIsError(true)
        setIsLoading(false)
        console.log(err)
    }
}

2. 未处理异常/无数据场景的loading状态

代码中仅在请求成功且有数据时关闭loading,else分支和catch块未设置setIsLoading(false),会导致组件一直显示Loading状态,同时recipes保持空数组,看起来像是状态未更新。

解决方法:
在else分支和catch块中都添加setIsLoading(false),确保无论请求结果如何,loading状态都会被正确关闭。

3. 状态更新的渲染触发问题

React的setState是异步的,且会对比新旧状态的引用。如果response.data.recipes的引用未变化(比如同一个数组对象),可能不会触发组件重新渲染。

解决方法:
通过扩展运算符创建新数组,强制触发状态更新:

setRecipes([...response.data.recipes])

修正后的完整代码

import Recipe from "./Recipe";
import Recipes from "./Recipes";
import { useState, useEffect } from "react";
import { useLocation } from "react-router-dom";
import axios from "axios";

const RenderRecipes = ({ isLoading, setIsLoading }) =>{
    const [search, setSearch] = useState('')
    const [isError, setIsError] = useState(false)
    const [recipes, setRecipes] = useState([])

    const location = useLocation()
    const [foodType, setFoodType] = useState('')

    
    const apiRequest = async (url) =>{
        return new Promise((resolve, reject) =>
            axios.get(url)
            .then(res => resolve(res.data))
            .catch(err => reject(err))
        )
    }

    const handleSearchSubmitChange = (e) => {
        setSearch(e.target.value)
    }
    
    const handleSearchSubmit = async (foodTypeParam) => {
        setIsError(false)
        setIsLoading(true)
        try{
            let q = search
            let type = foodTypeParam || foodType;
            let response = await axios(`https://api.spoonacular.com/recipes/complexSearch?apiKey=5ca51407f1a649a2b35aef72a28100b6&type=${type}&query=${q}`)
            console.log(response.data)
            if(Array.isArray(response.data.recipes)){
                console.log(response.data.recipes)
                setRecipes([...response.data.recipes])
                setIsLoading(false)
            } else {
                setIsError(true)
                setIsLoading(false)
            }
        }
        catch(err){
            setIsError(true)
            setIsLoading(false)
            console.log(err)
        }
    }


    useEffect(()=>{
        const currentFoodType = location.state?.type || '';
        setFoodType(currentFoodType);
        handleSearchSubmit(currentFoodType);
    },[location.state?.type])
    
    
    let mappedRecipes = recipes.map((recipe) => {
        return (
            <Recipe
                recipe={recipe}
                key={recipe.id} // 改用唯一id作为key,避免索引带来的渲染问题
                _id={recipe.id}
                img={recipe.image}
            />
        )
    })

    return(
        <div>
            {console.log(recipes)}
            {isLoading ? <div className="loading-screen"><h1>Loading</h1></div> : mappedRecipes}
            {isError && <div>请求出错或未找到数据</div>}
        </div>
    )
}

export default RenderRecipes;

额外优化点

  • 用recipe.id替代数组索引作为组件key,避免组件复用导致的异常;
  • 添加错误提示UI,提升用户体验;
  • 用可选链操作符location.state?.type避免location.state不存在时的报错。

内容的提问来源于stack exchange,提问作者JLB

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 03:54:55