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

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 };
}

问题原因

  1. 初始值与数据校验缺失:useFetch中data初始值为null,即便请求完成且无网络错误,也可能存在API返回数据无recipe字段的情况(比如无效ID返回空数据),此时!isLoading && !error为真,但data仍不满足渲染要求。
  2. 无效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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 01:00:21