React TypeScript中Context数据存在却无法map,报undefined错误求助
React TypeScript 遍历数组时出现 "undefined 无法执行 map" 错误
问题场景
React TypeScript项目中,访问URL触发Context内的函数拉取数据,数据返回后更新状态并传递给子组件,子组件尝试用map遍历数组时抛出"Cannot read properties of undefined (reading 'map')"错误,但控制台打印能看到数据确实存在。相同逻辑在JavaScript环境下正常运行。
相关代码
CategoryGallery 组件
import React from 'react' import { IMeal } from '../../_types/contexType' import styles from './CategoryGallery.module.scss' interface props { meals: IMeal[] } function CategoryGallery({meals}:props) { if(!meals) return <h1>Loading ...</h1> console.log(meals) return ( <div className={styles.gallery}> {meals.map((x,i) => { const {strMeal,strMealThumb} = x; return ( <figure className={`${styles.gallery__item} ${styles.gallery__item__[i]}`}> <img src={strMealThumb} alt={strMeal}/> <h6 className={styles.gallery__item__heading}>{strMeal}</h6> </figure> ) })} </div> ) } export default CategoryGallery
SingleRCategory 组件
import React, { useEffect } from 'react' import {useParams} from 'react-router-dom'; import CategoryFilter from '../../components/CategoryFiler/CategoryFilter'; import CategoryGallery from '../../components/CategoryGallery/CategoryGallery'; import { useGlobalContext } from '../../context'; import styles from './SingleRCategory.module.scss'; function SingleRCategory() { const {categoryname} = useParams<{categoryname?:string}>(); const {byCategory,category} = useGlobalContext() useEffect(() => { if(byCategory){ if(!categoryname) return; byCategory(categoryname) } },[categoryname,category]) const getCategoryTheme = ()=> { const Theme = new Map([ ["meat","meatTheme"], ["vegie","vegieTheme"], ["seaFood","seaFoodTheme"], ]); if(categoryname){ const selectedItem = Theme.get(categoryname); return selectedItem || ""; } return ""; } return ( <div className={`${styles.singleRcCategory} ${styles[getCategoryTheme()]}`}> {category && <CategoryFilter meals={category}/>} {category && <CategoryGallery meals={category}/>} </div> ) } export default SingleRCategory
Context Provider 代码
import { createContext, useContext, useEffect, useState } from "react"; import { IMealsContext, IMeal, IntroFood } from "./_types/contexType"; export const AppContext = createContext<Partial<IMealsContext>>({}); export type AppProviderProps = { children: React.ReactNode } export function AppProvider({children}: AppProviderProps){ const [category,setCategory] = useState<IMeal[]>(); const [singleMeal,setSingleMeal] = useState<IMeal>(); const [favoriteMeals,setFavoriteMeals] = useState<IMeal[]>(); const [introFood,setIntroFood] = useState<IntroFood[]>(); const dataFetcher = async (arr: string[]) => { const data= await Promise.all(arr.map(async (x) => { const meal = await fetch(`https://www.themealdb.com/api/json/v1/1/filter.php?c=${x}`); return meal.json(); })); const flattend: IMeal[] = data.flatMap((x) => x.meals); return flattend; } const forIntro = () => { fetch("https://www.themealdb.com/api/json/v1/1/search.php?f=h") .then(res => res.json()) .then(data => data.meals) .then((data:IMeal[]) => { return data.slice(1,3).map(x => { const {strMeal,strMealThumb,strIngredient1,strIngredient2,strIngredient3,strIngredient4} = x; const rating = Math.ceil(Math.random() * 10).toString(); const ingredients = [strIngredient1,strIngredient2,strIngredient3,strIngredient4]; return { img:strMealThumb, name:strMeal, rating, ingredients } as IntroFood; }) }) .then(result => setIntroFood(result)) } const mealMap = new Map([ ["meat",["Beef","Chicken","Goat","Lamp","Pork"]], ["vegie",["Vegan","Vegetarian"]], ["seaFood",["Seafood"]], ]) const byCategory = (category: string) => { const selectedCategory = mealMap.get(category); if(selectedCategory) dataFetcher(selectedCategory).then(res => setCategory(res)); } const forFeature = async () => { const letters = ["a","h","d"]; const data= await Promise.all(letters.map(async (x) => { const meal = await fetch(`https://www.themealdb.com/api/json/v1/1/search.php?f=${x}`); return meal.json(); })); const flattend: IMeal[] = data.flatMap((x) => x.meals); setFavoriteMeals(flattend.slice(2)); } useEffect(() => { forIntro() },[]) return <AppContext.Provider value={{category,singleMeal,introFood,byCategory,forFeature,favoriteMeals}}> {children} </AppContext.Provider> } export const useGlobalContext = () => { return useContext(AppContext) }
错误截图


问题根源
- 类型与初始状态不匹配:Context中
category初始值为undefined,但CategoryGallery的props要求meals是IMeal[]类型。尽管父组件用category && ...做了判断,但TypeScript严格模式下,组件初始化阶段category为undefined,此时props类型不兼容,且运行时可能存在渲染时机差,导致子组件接收到undefined。 - useEffect依赖错误:
SingleRCategory的useEffect依赖了category,每次category更新都会重新调用byCategory,触发setCategory,造成循环重渲染,状态波动过程中可能传递undefined给子组件。 - 接口数据兜底缺失:
dataFetcher中未处理接口返回meals为undefined的情况,flatMap处理undefined会导致最终返回数组异常。
解决办法
1. 修正子组件props类型
允许meals为undefined,并在组件内做兜底处理:
interface props { meals: IMeal[] | undefined } function CategoryGallery({meals}:props) { if(!meals) return <h1>Loading ...</h1> // 原有逻辑不变 }
或者给props设置默认空数组,避免判断:
interface props { meals?: IMeal[] } function CategoryGallery({meals = []}:props) { return ( <div className={styles.gallery}> {meals.map((x,i) => { // 原有逻辑不变 })} </div> ) }
2. 修复useEffect依赖项
移除category依赖,避免循环调用:
useEffect(() => { if(byCategory && categoryname){ byCategory(categoryname) } },[categoryname, byCategory])
3. 给Context状态设置初始空数组
将category初始化为空数组,从根源避免undefined:
const [category,setCategory] = useState<IMeal[]>([]);
此时父组件的判断可改为category.length > 0,或者子组件直接处理空数组。
4. 接口数据兜底
在dataFetcher中处理meals为undefined的情况:
const flattend: IMeal[] = data.flatMap((x) => x.meals || []);
内容的提问来源于stack exchange,提问作者irakli777
相关产品推荐
相关产品推荐

