React组件RecipeItemPage渲染forkify API数据时遇TypeError求助
使用forkify API渲染食谱组件时出现空值读取错误
问题详情
路由到RecipeItem组件时触发错误:TypeError: Cannot read properties of null (reading 'recipe')。已通过!isLoading && !error做条件渲染,但仍出现未获取数据就读取的情况。
涉及代码
RecipeItem 组件
import { useParams } from "react-router-dom"; import useFetch from "../../hooks/useFetch"; import { Oval } from "react-loader-spinner"; import RecipeItemPage from "./RecipeItemPage"; function RecipeItem() { const { id } = useParams(); console.log(id); const { data, error, loading: isLoading } = useFetch(id); console.log(data); return ( <div className="home container mx-auto py-8 flex flex-wrap gap-10 justify-center"> {isLoading && <Oval />} {!isLoading && !error && <RecipeItemPage data={data} />} </div> ); } export default RecipeItem;
RecipeItemPage 组件
function RecipeItemPage({ data }) { console.log(data); return ( <div className="recipe-item-section container mx-auto px-5 py-8 grid grid-cols-1 lg:grid-cols-2 gap-8 lg:px-0"> <div className="recipe-img-sec flex flex-col gap-5"> <div className="img overflow-hidden rounded-xl border shadow-md lg:h-96 group"></div> <img className="w-full h-full object-cover group-hover:scale-105 duration-300" src={data.recipe.image_url} /> </div> </div> ); } export default RecipeItemPage;
useFetch Hook
export default function useFetch(url) { const [data, setData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { (async function () { try { setLoading(true); const response = await fetch( `https://forkify-api.herokuapp.com/api/get?rId=${url}` ); const data = await response.json(); setData(data); } catch (err) { setError(err); } finally { setLoading(false); } })(); }, [url]); return { data, error, loading }; }
问题原因
- 初始值与数据校验缺失:
useFetch中data初始值为null,即便请求完成且无网络错误,也可能存在API返回数据无recipe字段的情况(比如无效ID返回空数据),此时!isLoading && !error为真,但data仍不满足渲染要求。 - 无效HTML结构:原
RecipeItemPage中img标签脱离了父容器div,即便数据正常也会导致渲染异常。
修复方案
1. 强化渲染前的数据校验
修改RecipeItem的条件渲染逻辑,确保data和data.recipe都存在再渲染组件,同时增加错误状态的展示:
return ( <div className="home container mx-auto py-8 flex flex-wrap gap-10 justify-center"> {isLoading && <Oval />} {error && <div className="text-red-500">加载出错:{error.message}</div>} {!isLoading && !error && data?.recipe ? ( <RecipeItemPage data={data} /> ) : ( !isLoading && <div className="text-gray-500">未找到对应食谱数据</div> )} </div> );
2. 组件内增加空值保护
在RecipeItemPage中使用可选链操作符?.避免直接读取空值,同时修正HTML结构:
function RecipeItemPage({ data }) { console.log(data); return ( <div className="recipe-item-section container mx-auto px-5 py-8 grid grid-cols-1 lg:grid-cols-2 gap-8 lg:px-0"> <div className="recipe-img-sec flex flex-col gap-5"> <div className="img overflow-hidden rounded-xl border shadow-md lg:h-96 group"> <img className="w-full h-full object-cover group-hover:scale-105 duration-300" src={data?.recipe?.image_url || 'https://via.placeholder.com/800x600?text=暂无图片'} alt="食谱封面" /> </div> </div> </div> ); }
3. 完善Hook的数据校验
在useFetch中增加API返回数据的校验,若数据不符合预期则标记为错误:
export default function useFetch(url) { const [data, setData] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { (async function () { try { setLoading(true); setError(null); const response = await fetch( `https://forkify-api.herokuapp.com/api/get?rId=${url}` ); // 校验HTTP响应状态 if (!response.ok) { throw new Error(`请求失败:${response.status}`); } const resData = await response.json(); // 校验是否包含有效食谱数据 if (!resData?.recipe) { throw new Error("未获取到有效食谱信息"); } setData(resData); } catch (err) { setError(err); setData(null); } finally { setLoading(false); } })(); }, [url]); return { data, error, loading }; }
内容的提问来源于stack exchange,提问作者Billy Bongso
相关产品推荐
相关产品推荐

