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

React 19自定义Hook引发组件无限循环问题求助

React 19组件无限循环问题排查与修复

问题原因

  • 渲染阶段直接调用setState:组件每次渲染时,只要menuList存在,就执行setRestaurantInfo和setFilteredVegList,这两个setState会触发组件重新渲染,重新渲染后又会重复执行这段代码,形成无限循环。
  • 自定义Hook依赖缺失:useRestaurantMenu的useEffect依赖为空数组,当resId变化时不会重新发起请求,导致数据不更新,同时可能间接引发不必要的渲染逻辑。
  • 初始状态类型不匹配:restaurantInfo和filteredVegList初始值设为空字符串'',但实际要存储对象/数组,类型不匹配会导致!restaurantInfo的判断逻辑出现异常,触发额外渲染。

解决方案

1. 将状态更新逻辑移至useEffect

把原本在渲染阶段的setState放到useEffect中,依赖menuList,确保只有当menuList变化时才更新状态,避免重复触发渲染。

2. 修复自定义Hook的依赖问题

在useRestaurantMenu的useEffect中加入resId作为依赖,确保resId变化时重新请求数据,避免 stale closure 问题。

3. 修正初始状态类型

将restaurantInfo初始值设为null,filteredVegList初始值设为[],匹配实际存储的数据类型,让判断逻辑更准确。

4. 优化筛选逻辑

不需要每次修改isVeg都重置filteredVegList,用useMemo缓存筛选结果,避免每次渲染重新计算,提升性能。


修改后的RestaurantItems组件

const RestaurantItems = () => {
    const [restaurantInfo, setRestaurantInfo] = useState(null);
    const [filteredVegList, setFilteredVegList] = useState([]);
    const [isVeg, setIsVeg] = useState(false);

    const { resId } = useParams();
    const { state } = useLocation();
    const { image } = state || {};

    const menuList = useRestaurantMenu(resId);

    // 仅在menuList变化时更新状态
    useEffect(() => {
        if (menuList) {
            setRestaurantInfo(menuList?.cards[2]?.card?.card?.info);
            setFilteredVegList(menuList?.cards[4]?.groupedCard?.cardGroupMap?.REGULAR?.cards || []);
        }
    }, [menuList]);

    // 用useMemo缓存筛选后的列表
    const displayList = useMemo(() => {
        if (!filteredVegList.length) return [];
        return filteredVegList.map(item => {
            if (!item.card.card.title || !item.card.card.itemCards || !Array.isArray(item.card.card.itemCards)) {
                return null;
            }
            // 根据isVeg状态筛选菜品
            const filteredItems = isVeg 
                ? item.card.card.itemCards.filter(menuItem => 
                    menuItem.card.info.itemAttribute.vegClassifier === 'VEG'
                  ) 
                : item.card.card.itemCards;
            
            return {
                ...item,
                card: {
                    ...item.card,
                    card: {
                        ...item.card.card,
                        itemCards: filteredItems
                    }
                }
            };
        }).filter(Boolean);
    }, [filteredVegList, isVeg]);

    return (!restaurantInfo) ? <ShimmerUI /> : (
        <div>
            <img className="restaurant-image" src={IMAGE_URL + image} alt="menu-items" />
            <div>{restaurantInfo.name}</div>
            <div>{restaurantInfo.avgRating} - {restaurantInfo.costForTwoMessage}</div>
            <div>{restaurantInfo.locality}</div>
            <div>{restaurantInfo.cuisines.join(", ")}</div>

            <label className="switch">
                <input 
                    type="checkbox" 
                    checked={isVeg}
                    onChange={(e)=> setIsVeg(e.currentTarget.checked)}
                />
                <span className="slider"></span>
            </label>

            {
                displayList.map((item, index) => (
                    <div key={"menu-" + index}>
                        <h1>{item.card.card.title}</h1>
                        {item.card.card.itemCards.map((menuItem) => (
                            <div key={menuItem.card.info.id} className="menu-item-list">
                                <img src={IMAGE_URL + menuItem.card.info.imageId} alt="menu-items" />
                                <p>{menuItem.card.info.name} ({menuItem.card.info.itemAttribute.vegClassifier}) - Rs.{menuItem.card.info.price / 100}/-</p>
                            </div>
                        ))}
                    </div>
                ))
            }
        </div>
    )
}

export default RestaurantItems;

修改后的useRestaurantMenu Hook

const useRestaurantMenu = (resId) => {
    const [menuList, setMenuList] = useState(null);

    useEffect(()=> {
        const fetchData = async () => {
            const response = await fetch(RESTAURANT_DATA_URL.replace('$restaurantId$', resId));
            const json = await response.json();
            setMenuList(json.data);
        };
        fetchData();
    }, [resId]) // 添加resId作为依赖,确保参数变化时重新请求

    return menuList;
}

export default useRestaurantMenu;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 10:49:52