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

React新闻页map中使用useModal触发Hook报错,求解决方案

解决NextUI useModal在map循环中触发的Hook错误

问题场景

开发新闻列表页面,需求是用户点击单条新闻卡片时弹出模态框展示完整内容,但在map循环中调用NextUI的useModal Hook时,触发以下错误:

Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement in React Hooks.

错误原因

React Hook的核心规则之一:不能在循环、条件判断、嵌套函数中调用Hook。你在SortingDate(data).map(...)的回调里调用useModal,违反了这条规则——每次渲染时循环的次数可能变化,导致React无法保证Hook调用顺序的一致性,从而抛出错误。

可行解决方案

方案一:将新闻卡片抽离为独立组件

把单条新闻的渲染逻辑和模态框Hook封装到单独的组件中,让useModal在组件的顶层调用,符合Hook规则。

修改后的代码

首先修改News组件:

import SortingDate from "@/functions/Sorting";
import { useState, useEffect } from "react";
import Loading from "./loading";
import NewsCard from "./NewsCard"; // 导入新组件

function News() {
  const [data, setData] = useState(null);
  const [isLoading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    fetch("/api/news")
      .then((res) => res.json())
      .then((data) => {
        setData(data);
        setLoading(false);
      });
  }, []);

  if (isLoading) return <Loading />;
  if (!data) return <Loading />;

  return (
    <div className="py-24">
      <div className="mx-auto max-w-7xl px-6 lg:px-8">
        <div className="mx-auto max-w-2xl text-center">
          <h2 className="text-3xl font-bold tracking-tight text-white sm:text-4xl">
            Here comes the tea
          </h2>
          <p className="mt-2 text-lg leading-8 text-gray-300">
            Keep in touch with the latest news.
          </p>
        </div>

        <div className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-x-8 gap-y-20 lg:mx-0 lg:max-w-none lg:grid-cols-3">
          {/* 循环渲染NewsCard组件 */}
          {SortingDate(data).map((post) => (
            <NewsCard key={post.id} post={post} />
          ))}
        </div>
      </div>
    </div>
  );
}

然后创建NewsCard.jsx组件:

import { useModal } from "@nextui-org/react";
import NewsModal from "@/components/ui/news-modal";

function NewsCard({ post }) {
  // 在组件顶层合法调用useModal
  const { setVisible, bindings } = useModal();

  return (
    <button
      onClick={() => setVisible((prevState) => !prevState)}
      className="flex flex-col items-start bg-gray-800 rounded-2xl"
    >
      <NewsModal
        setVisible={setVisible}
        bindings={bindings}
        id={post.id}
      />
      <article>
        <div className="relative w-full">
          <img
            src={post.imageUrl}
            alt="Thumbnail"
            className="aspect-[16/9] w-full rounded-2xl bg-gray-300 object-cover sm:aspect-[2/1] lg:aspect-[3/2]"
          />
          <div className="absolute inset-0 rounded-2xl ring-1 ring-inset ring-gray-900/10" />
        </div>
        <div className="max-w-xl">
          <div className="mt-8 flex items-center gap-x-4 text-xs mx-2">
            <time dateTime={post.datetime} className="text-gray-300">
              {post.date}
            </time>
          </div>
          <div className="group relative mx-2">
            <h3 className="mt-3 text-lg font-semibold leading-6 text-gray-300 group-hover:text-gray-500">
              <a href={post.href}>
                <span className="absolute inset-0" />
                {post.title}
              </a>
            </h3>
          </div>
          <div className="relative mt-8 flex items-center gap-x-4 mx-2 my-2">
            <div className="text-sm leading-6">
              <p className="font-semibold text-white">
                <a>
                  <span className="absolute inset-0" />
                  {post.author.name}
                </a>
              </p>
            </div>
          </div>
        </div>
      </article>
    </button>
  );
}

export default NewsCard;

方案二:使用单个模态框控制显示内容

如果不想创建多个组件,可以在News组件中只初始化一个模态框,通过状态记录当前选中的新闻ID,点击时更新状态,模态框根据ID展示对应内容。这种方式更高效,避免创建多个模态框实例。

修改后的代码

import SortingDate from "@/functions/Sorting";
import { useModal } from "@nextui-org/react";
import NewsModal from "@/components/ui/news-modal";
import { useState, useEffect } from "react";
import Loading from "./loading";

function News() {
  const [data, setData] = useState(null);
  const [isLoading, setLoading] = useState(false);
  const [selectedPostId, setSelectedPostId] = useState(null);
  // 在组件顶层调用useModal,只创建一个实例
  const { setVisible, bindings } = useModal();

  useEffect(() => {
    setLoading(true);
    fetch("/api/news")
      .then((res) => res.json())
      .then((data) => {
        setData(data);
        setLoading(false);
      });
  }, []);

  if (isLoading) return <Loading />;
  if (!data) return <Loading />;

  // 获取当前选中的新闻详情
  const selectedPost = data.find(post => post.id === selectedPostId);

  return (
    <div className="py-24">
      <div className="mx-auto max-w-7xl px-6 lg:px-8">
        <div className="mx-auto max-w-2xl text-center">
          <h2 className="text-3xl font-bold tracking-tight text-white sm:text-4xl">
            Here comes the tea
          </h2>
          <p className="mt-2 text-lg leading-8 text-gray-300">
            Keep in touch with the latest news.
          </p>
        </div>

        {/* 单个模态框,根据selectedPost展示内容 */}
        <NewsModal
          setVisible={setVisible}
          bindings={bindings}
          post={selectedPost} // 传递选中的新闻数据
        />

        <div className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-x-8 gap-y-20 lg:mx-0 lg:max-w-none lg:grid-cols-3">
          {SortingDate(data).map((post) => (
            <button
              onClick={() => {
                setSelectedPostId(post.id); // 记录选中的新闻ID
                setVisible(true); // 打开模态框
              }}
              key={post.id}
              className="flex flex-col items-start bg-gray-800 rounded-2xl"
            >
              <article>
                <div className="relative w-full">
                  <img
                    src={post.imageUrl}
                    alt="Thumbnail"
                    className="aspect-[16/9] w-full rounded-2xl bg-gray-300 object-cover sm:aspect-[2/1] lg:aspect-[3/2]"
                  />
                  <div className="absolute inset-0 rounded-2xl ring-1 ring-inset ring-gray-900/10" />
                </div>
                <div className="max-w-xl">
                  <div className="mt-8 flex items-center gap-x-4 text-xs mx-2">
                    <time dateTime={post.datetime} className="text-gray-300">
                      {post.date}
                    </time>
                  </div>
                  <div className="group relative mx-2">
                    <h3 className="mt-3 text-lg font-semibold leading-6 text-gray-300 group-hover:text-gray-500">
                      <a href={post.href}>
                        <span className="absolute inset-0" />
                        {post.title}
                      </a>
                    </h3>
                  </div>
                  <div className="relative mt-8 flex items-center gap-x-4 mx-2 my-2">
                    <div className="text-sm leading-6">
                      <p className="font-semibold text-white">
                        <a>
                          <span className="absolute inset-0" />
                          {post.author.name}
                        </a>
                      </p>
                    </div>
                  </div>
                </div>
              </article>
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

同时需要调整NewsModal组件,接收post props来展示对应内容,而不是仅通过id获取。

内容的提问来源于stack exchange,提问作者Lim Jian Hong

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 17:22:10