Next.js 14客户端组件调用Server Actions时async/await报错求助
在Next.js 14.0.1中客户端组件调用Server Action实现无限滚动的解决方案
问题说明
在Next.js 14.0.1构建无限滚动功能时,客户端组件内直接通过async/await调用Server Action(getData)触发以下错误:
"async/await is not yet supported in Client Components, only Server Components."
服务端组件page.tsx可正常调用该Server Action获取初始数据,但客户端组件InfiniteScrollData.tsx调用时出错。
现有代码片段
page.tsx(服务端组件)
// page.tsx const Home = async ({ searchParams }) => { const searchKeyword = typeof searchParams.keyword === "string" ? searchParams.keyword : ""; let apiResponse = await getPhotos({ query: searchKeyword }); return ( <main className='p-6 flex justify-center w-full min-h-screen bg-black'> <InfiniteScrollData search={searchKeyword} initialData={apiResponse?.results || []} /> </main> ); }; export default Home;
actions.ts(Server Action)
// actions.ts "use server"; export const getData = async ({ query, }) => { const { response } = await fetchData({ query, }); apiResponse = response; return response; };
InfiniteScrollData.tsx(客户端组件)
// InfiniteScrollData.tsx "use client"; // imports const InfiniteScrollImages = ({ search, initialData, }) => { const [data, setData] = useState(initialData); const [page, setPage] = useState(1); const [loaderRef, inView] = useInView(); const loadMoreData = useCallback(async () => { const next = page + 1; const response = await getData({ query: search, page: next }); if (response?.results?.length) { setPage(next); setData((prev) => [...prev, ...(response?.results || [])]); } }, [page, search]); useEffect(() => { if (inView) { loadMoreData(); } }, [inView, loadMoreData]); return ( <> <div> {data?.map((item) => ( <Card key={item.id} data={item} /> ))} </div> <div ref={loaderRef}> <Loader /> </div> </> ); }; export default InfiniteScrollImages;
可行解决方案
1. 修正客户端组件的异步调用逻辑
错误的核心是不能在组件渲染阶段使用async/await,但在useCallback、useEffect这类回调函数中调用异步Server Action是完全允许的。只需给现有代码添加加载状态锁和错误处理即可:
"use client"; import { useState, useCallback, useEffect } from 'react'; import { getData } from './actions'; const InfiniteScrollImages = ({ search, initialData, }) => { const [data, setData] = useState(initialData); const [page, setPage] = useState(1); const [isLoading, setIsLoading] = useState(false); // 新增加载状态,防止重复请求 const [loaderRef, inView] = useInView(); const loadMoreData = useCallback(async () => { if (isLoading) return; // 加载中时拒绝新请求 setIsLoading(true); try { const nextPage = page + 1; const response = await getData({ query: search, page: nextPage }); if (response?.results?.length) { setPage(nextPage); setData(prev => [...prev, ...response.results]); } } catch (error) { console.error('加载更多数据失败:', error); // 这里可以添加前端错误提示逻辑 } finally { setIsLoading(false); // 无论成功失败都重置加载状态 } }, [page, search, isLoading]); useEffect(() => { if (inView && !isLoading) { loadMoreData(); } }, [inView, loadMoreData, isLoading]); return ( <> <div> {data?.map(item => ( <Card key={item.id} data={item} /> ))} </div> {isLoading && ( <div ref={loaderRef}> <Loader /> </div> )} </> ); }; export default InfiniteScrollImages;
2. 用startTransition优化UI体验
如果加载数据时不想阻塞用户交互,可以用React的startTransition包裹状态更新,标记这是一个非紧急更新:
import { startTransition } from 'react'; // ... 其他代码不变,修改loadMoreData中的状态更新部分 const loadMoreData = useCallback(async () => { if (isLoading) return; setIsLoading(true); try { const nextPage = page + 1; const response = await getData({ query: search, page: nextPage }); if (response?.results?.length) { startTransition(() => { setPage(nextPage); setData(prev => [...prev, ...response.results]); }); } } catch (error) { console.error('加载更多数据失败:', error); } finally { setIsLoading(false); } }, [page, search, isLoading]);
3. 备选:通过API路由调用Server Action
如果你更习惯传统API调用模式,可以把Server Action封装为API路由,再用fetch在客户端调用:
步骤1:创建API路由
// app/api/get-photos/route.ts import { getData } from '@/actions'; import { NextResponse } from 'next/server'; export async function POST(request: Request) { const { query, page } = await request.json(); const result = await getData({ query, page }); return NextResponse.json(result); }
步骤2:客户端组件中用fetch调用
const loadMoreData = useCallback(async () => { if (isLoading) return; setIsLoading(true); try { const nextPage = page + 1; const res = await fetch('/api/get-photos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: search, page: nextPage }), }); if (!res.ok) throw new Error('请求失败'); const response = await res.json(); if (response?.results?.length) { setPage(nextPage); setData(prev => [...prev, ...response.results]); } } catch (error) { console.error('加载更多数据失败:', error); } finally { setIsLoading(false); } }, [page, search, isLoading]);
内容的提问来源于stack exchange,提问作者Haider Ali Anjum
相关产品推荐
相关产品推荐

