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
相关产品推荐
相关产品推荐

