React项目TypeError修复:解决读取null的map属性错误
React项目报错修复:TypeError: Cannot read properties of null (reading 'map')
问题详情
项目运行时触发错误:act-dom.production.min.js:216 TypeError: Cannot read properties of null (reading 'map'),错误定位在Home.js:68:23。
原始代码
import React, { useEffect, useState } from 'react' import Card from '../components/Card' // import Carousel from '../components/Carousel' import Footer from '../components/Footer' import Navbar from '../components/Navbar' export default function Home() { const [foodCat, setFoodCat] = useState([]) const [foodItems, setFoodItems] = useState([]) const [search, setSearch] = useState('') const loadFoodItems = async () => { let response = await fetch("https://meal-express-backend.vercel.app/api/auth/foodData", { // credentials: 'include', // Origin:"http://localhost:3000/login", method: 'POST', headers: { 'Content-Type': 'application/json' } }); response = await response.json() // console.log(response[1][0].CategoryName) setFoodItems(response[0]) setFoodCat(response[1]) } useEffect(() => { loadFoodItems() }, []) return ( <div > <div> <Navbar /> </div> <div> <div id="carouselExampleFade" className="carousel slide carousel-fade " data-bs-ride="carousel"> <div className="carousel-inner " id='carousel'> <div className=" carousel-caption " style={{ zIndex: "9" }}> <div className=" d-flex justify-content-center"> {/* justify-content-center, copy this <form> from navbar for search box */} <input className="form-control me-2 w-75 bg-white text-dark" type="search" placeholder="Search in here..." aria-label="Search" value={search} onChange={(e) => { setSearch(e.target.value) }} /> <button className="btn text-white bg-danger" onClick={() => { setSearch('') }}>X</button> </div > </div > <div className="carousel-item active" > <img src="https://source.unsplash.com/random/900x700/?burger" className="d-block w-100 " style={{ filter: "brightness(30%)" }} alt="..." /> </div> <div className="carousel-item"> <img src="https://source.unsplash.com/random/900x700/?pastry" className="d-block w-100 " style={{ filter: "brightness(30%)" }} alt="..." /> </div> <div className="carousel-item"> <img src="https://source.unsplash.com/random/900x700/?barbeque" className="d-block w-100 " style={{ filter: "brightness(30%)" }} alt="..." /> </div> </div > <button className="carousel-control-prev" type="button" data-bs-target="#carouselExampleFade" data-bs-slide="prev"> <span className="carousel-control-prev-icon" aria-hidden="true"></span> <span className="visually-hidden">Previous</span> </button> <button className="carousel-control-next" type="button" data-bs-target="#carouselExampleFade" data-bs-slide="next"> <span className="carousel-control-next-icon" aria-hidden="true"></span> <span className="visually-hidden">Next</span> </button> </div > </div > <div className='container'> {/* boootstrap is mobile first */} { foodCat !== [] ? foodCat.map((data) => { return ( // justify-content-center <div className='row mb-3'> <div key={data.id} className='fs-3 m-3'> {data.CategoryName} </div> <hr id="hr-success" style={{ height: "4px", backgroundImage: "-webkit-linear-gradient(left,rgb(0, 255, 137),rgb(0, 0, 0))" }} /> {foodItems !== [] ? foodItems.filter( (items) => (items.CategoryName === data.CategoryName) && (items.name.toLowerCase().includes(search.toLowerCase()))) .map(filterItems => { return ( <div key={filterItems.id} className='col-12 col-md-6 col-lg-3'> {console.log(filterItems.url)} <Card foodName={filterItems.name} item={filterItems} options={filterItems.options[0]} ImgSrc={filterItems.img} ></Card> </div> ) }) : <div> No Such Data </div>} </div> ) }) : ""} </div> <Footer /> </div > ) }
错误分析
- API请求无异常处理:未捕获请求失败或返回非预期数据的情况,若后端返回
null而非数组,调用map会直接报错。 - 数组空值判断错误:
foodCat !== []无法正确判断数组是否为空,因为数组是引用类型,直接比较地址永远不相等,正确判断应为foodCat.length > 0。 - 嵌套属性未做空校验:直接访问
filterItems.options[0],若options为null或非数组,会触发读取null下标错误。
修正后的代码
import React, { useEffect, useState } from 'react' import Card from '../components/Card' // import Carousel from '../components/Carousel' import Footer from '../components/Footer' import Navbar from '../components/Navbar' export default function Home() { const [foodCat, setFoodCat] = useState([]) const [foodItems, setFoodItems] = useState([]) const [search, setSearch] = useState('') const loadFoodItems = async () => { try { let response = await fetch("https://meal-express-backend.vercel.app/api/auth/foodData", { // credentials: 'include', // Origin:"http://localhost:3000/login", method: 'POST', headers: { 'Content-Type': 'application/json' } }); if (!response.ok) throw new Error('请求失败') response = await response.json() // 确保返回的数据为数组,避免null setFoodItems(Array.isArray(response[0]) ? response[0] : []) setFoodCat(Array.isArray(response[1]) ? response[1] : []) } catch (error) { console.error('加载食品数据失败:', error) setFoodItems([]) setFoodCat([]) } } useEffect(() => { loadFoodItems() }, []) return ( <div > <div> <Navbar /> </div> <div> <div id="carouselExampleFade" className="carousel slide carousel-fade " data-bs-ride="carousel"> <div className="carousel-inner " id='carousel'> <div className=" carousel-caption " style={{ zIndex: "9" }}> <div className=" d-flex justify-content-center"> <input className="form-control me-2 w-75 bg-white text-dark" type="search" placeholder="Search in here..." aria-label="Search" value={search} onChange={(e) => { setSearch(e.target.value) }} /> <button className="btn text-white bg-danger" onClick={() => { setSearch('') }}>X</button> </div > </div > <div className="carousel-item active" > <img src="https://source.unsplash.com/random/900x700/?burger" className="d-block w-100 " style={{ filter: "brightness(30%)" }} alt="..." /> </div> <div className="carousel-item"> <img src="https://source.unsplash.com/random/900x700/?pastry" className="d-block w-100 " style={{ filter: "brightness(30%)" }} alt="..." /> </div> <div className="carousel-item"> <img src="https://source.unsplash.com/random/900x700/?barbeque" className="d-block w-100 " style={{ filter: "brightness(30%)" }} alt="..." /> </div> </div > <button className="carousel-control-prev" type="button" data-bs-target="#carouselExampleFade" data-bs-slide="prev"> <span className="carousel-control-prev-icon" aria-hidden="true"></span> <span className="visually-hidden">Previous</span> </button> <button className="carousel-control-next" type="button" data-bs-target="#carouselExampleFade" data-bs-slide="next"> <span className="carousel-control-next-icon" aria-hidden="true"></span> <span className="visually-hidden">Next</span> </button> </div > </div > <div className='container'> { foodCat && foodCat.length > 0 ? foodCat.map((data) => { return ( <div className='row mb-3' key={data.id}> <div className='fs-3 m-3'> {data.CategoryName} </div> <hr id="hr-success" style={{ height: "4px", backgroundImage: "-webkit-linear-gradient(left,rgb(0, 255, 137),rgb(0, 0, 0))" }} /> {foodItems && foodItems.length > 0 ? foodItems.filter( (items) => (items.CategoryName === data.CategoryName) && (items.name.toLowerCase().includes(search.toLowerCase()))) .map(filterItems => { // 避免options为null时报错 const firstOption = filterItems.options && Array.isArray(filterItems.options) ? filterItems.options[0] : {} return ( <div key={filterItems.id} className='col-12 col-md-6 col-lg-3'> {console.log(filterItems.url)} <Card foodName={filterItems.name} item={filterItems} options={firstOption} ImgSrc={filterItems.img} ></Card> </div> ) }) : <div>暂无数据</div>} </div> ) }) : <div>加载中...</div>} </div> <Footer /> </div > ) }
修复说明
- 添加API请求异常处理:用
try/catch包裹请求逻辑,捕获请求失败或数据解析错误,确保状态始终为数组。 - 修正数组空值判断:改用
foodCat.length > 0判断数组是否有内容,同时增加foodCat &&的空值校验。 - 嵌套属性空值保护:访问
options[0]前先校验options是否为数组,避免读取null的下标。 - 优化用户提示:将英文提示改为中文,加载状态显示"加载中..."提升用户体验。
内容的提问来源于stack exchange,提问作者Utkarsh Jain
相关产品推荐
相关产品推荐

