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
相关产品推荐
相关产品推荐

