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

NextJS中动态API调用组件遇未处理运行时错误的解决方法

动态调用IGDB API时的运行时错误修复

问题描述

我是Web开发新手,正在做一个游戏猜歌网站,核心功能是用户收听游戏原声后猜测对应游戏。为了让用户选择游戏,我做了一个输入框,输入游戏名时动态调用IGDB API获取游戏数据。硬编码搜索参数时API能正常工作,但在添加'use client'的页面组件里用useState监听输入状态,再把参数传给Search组件时,出现未处理的运行时错误。移除'use client'或者手动传入参数时,API调用又恢复正常。

错误原因

问题出在Next.js的组件渲染机制上:

  • 你写的Search是异步服务器组件(async函数组件),默认在服务器端执行异步API调用。
  • 而Tune页面是客户端组件(加了'use client'),客户端组件在浏览器环境中渲染,无法直接渲染异步服务器组件。当客户端状态(searchGame)更新时,React尝试在客户端重新渲染异步的Search组件,就会触发运行时错误。

解决方案

下面是两种可行的修复思路:

方案1:将Search组件改为客户端组件,内部处理API调用

把Search改成客户端组件,用useState存储游戏数据,useEffect监听搜索参数变化触发API请求,避免在客户端渲染异步组件。

修改后的Search组件代码:

"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import CustomImage from "@/components/ui/game-ui/custom-image";
import api from "@/api/igdb";

export default function Search({ searchParams }: { searchParams: { name: string } }) {
  const [games, setGames] = useState<any[]>([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const fetchGames = async () => {
      setLoading(true);
      try {
        const result = await api.search(searchParams);
        setGames(result);
      } catch (err) {
        console.error("搜索游戏失败:", err);
        setGames([]);
      } finally {
        setLoading(false);
      }
    };

    // 避免空搜索请求
    if (searchParams.name.trim()) {
      fetchGames();
    } else {
      setGames([]);
    }
  }, [searchParams]);

  if (loading) {
    return <div className="px-4 xl:px-40 mt-4">加载中...</div>;
  }

  return (
    <div className="px-4 xl:px-40">
      {games.length ? (
        <div className="mt-4 grid grid-cols-3 sm:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-y-6 gap-x-2">
          {games.map((game) => (
            <Link key={game.id} href={`/games/${game.id}`}>
              <figure>
                <div className="relative aspect-[3/4] rounded-xl c-bg-dark-blue transition md:hover:brightness-110">
                  <CustomImage {...game} />
                </div>
                <figcaption className="mt-2.5 text-xs sm:mt-3 sm:text-base font-bold text-center line-clamp-3">
                  {game.name}
                </figcaption>
              </figure>
            </Link>
          ))}
        </div>
      ) : (
        <h2 className="mt-4">未找到游戏</h2>
      )}
    </div>
  );
}

方案2:在客户端页面处理API调用,传递结果给纯展示组件

保持Search为纯展示组件,把API调用移到Tune页面(客户端组件)中,用useState和useEffect获取数据后,再传给Search组件渲染。

修改后的Tune页面代码:

"use client";
import { useState, useEffect } from "react";
import { TunePlayer } from "@/components/ui/audio-player/tune-player";
import Search from "@/components/ui/game-ui/search";
import { Input } from "@/components/ui/input";
import api from "@/api/igdb";

export default function Tune() {
  const [searchGame, setSearchGame] = useState({ name: "Kingdom Hearts" });
  const [games, setGames] = useState<any[]>([]);
  const [loading, setLoading] = useState(false);

  const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setSearchGame({ name: event.target.value });
  };

  useEffect(() => {
    const fetchGames = async () => {
      setLoading(true);
      try {
        const result = await api.search(searchGame);
        setGames(result);
      } catch (err) {
        console.error("搜索失败:", err);
        setGames([]);
      } finally {
        setLoading(false);
      }
    };

    if (searchGame.name.trim()) {
      fetchGames();
    } else {
      setGames([]);
    }
  }, [searchGame]);

  return (
    <>
      <TunePlayer />
      <div className="flex place-content-center place-items-center lg:flex-row gap-8 sm:gap-6 m-auto max-w-80">
        <Input
          type="search"
          placeholder="搜索游戏"
          onChange={handleSearchChange}
        />
      </div>
      <div className="outline-8 outline-white justify-items-center grid gap-3 p-6">
        <p>正在搜索:{searchGame.name}</p>
        {loading ? (
          <div>加载中...</div>
        ) : (
          <Search games={games} />
        )}
      </div>
    </>
  );
}

对应的纯展示Search组件代码:

import Link from "next/link";
import CustomImage from "@/components/ui/game-ui/custom-image";

export default function Search({ games }: { games: any[] }) {
  return (
    <div className="px-4 xl:px-40">
      {games.length ? (
        <div className="mt-4 grid grid-cols-3 sm:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-y-6 gap-x-2">
          {games.map((game) => (
            <Link key={game.id} href={`/games/${game.id}`}>
              <figure>
                <div className="relative aspect-[3/4] rounded-xl c-bg-dark-blue transition md:hover:brightness-110">
                  <CustomImage {...game} />
                </div>
                <figcaption className="mt-2.5 text-xs sm:mt-3 sm:text-base font-bold text-center line-clamp-3">
                  {game.name}
                </figcaption>
              </figure>
            </Link>
          ))}
        </div>
      ) : (
        <h2 className="mt-4">未找到游戏</h2>
      )}
    </div>
  );
}

额外建议

  • 给API调用添加防抖逻辑(比如用setTimeout延迟执行),避免用户输入时频繁触发请求。
  • 完善错误处理逻辑,防止API请求失败导致页面崩溃。
  • 确保api模块中的getToken逻辑在客户端请求时已完成token初始化。

内容的提问来源于stack exchange,提问作者ilukasschneider

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 22:53:10