Next.js 14服务端组件内嵌客户端组件加载按钮不显示问题
问题描述
我用Next.js 14开发预订应用,/search-result是服务端组件,负责展示从后端获取的行程数据,页面里渲染客户端组件FerryCards,每张卡片有个「Book Now」按钮,点击后要把选中项数据存入Cookie,再跳转到/booking页面。
相关代码如下:
BookingHandler函数
const BookingHandler = (data:any)=>{ setLoading(true) const bookingSessing = { from: parameters?.from, where: parameters?.where, travel_date: item?.travel_date, arrival_time: item?.arrival_time, departure_time: item?.departure_time, passengers: parameters?.passengers, shipper: data?.shipper, seatclass: data?.class, ship_id: data?.ship_id, class_id: data?.class_id, schedule_id: data?.schedule_id, price: data?.price, token: token, } let encryptBookingSession = encryptdata(JSON.stringify(bookingSessing)) setCookie('bookingSession', encryptBookingSession); router.push(`/booking`, ) setLoading(false) }
按钮组件
<Button size='sm' onClick={() => BookingHandler(_i)}> {Loading ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : null} Book </Button>
/search-result页面代码
import Header from '@/components/Header' import SearchForm from '@/components/SearchForm' import FerryCards from '@/components/cards/FerryCards' import { fetchFerry } from '@/lib/network-request/fetchFerry' import React from 'react' type SearchParam = { from: string, where: string, departure: string, passengers: string, } type SearchParamPops = { searchParams: SearchParam } type FerryData = { travel_date: string; departure_time: string; arrival_time: string; options: object; }; const page = async ({ searchParams }: SearchParamPops) => { const parameters = { from: searchParams.from, where: searchParams.where, departure: searchParams.departure, passengers: searchParams.passengers } const response: any = await fetchFerry(parameters.from, parameters.where, parameters.departure, parameters.passengers) response?.data?.sort((a: FerryData, b: FerryData) => { return parseInt(a?.departure_time?.replace(":", "")) - parseInt(b?.departure_time?.replace(":", "")); }); return ( <> <Header/> <section> <div className="mx-auto max-w-7xl p-6 lg:px-8"> <h1 className="text-4xl font-bold pb-3">Your Trip Results</h1> <h2 className="pb-3"> Date of trip: <span className="italic ml-2"> {parameters.departure} </span> </h2> <hr className="mb-5" /> <h3 className="font-semibold text-xl"> {response?.data?.length} results found </h3> <div className="space-y-2 mt-5"> {response?.data?.map((item: any, index: number) => { return ( <FerryCards key={index} item={item} parameters={parameters} token={response?.token} /> ) })} </div> </div> </section> </> ) } export default page
当前问题:
- 点击按钮后需等待1-2秒才跳转到
/booking页面 - 加载Spinner不显示,但手动将Loading状态设为true时可正常显示
- BookingHandler函数位于客户端组件FerryCards内
问题原因与解决方案
核心问题分析
- React状态更新异步特性:
setLoading(true)是异步操作,调用后React不会立即更新组件状态。你紧接着执行加密、存Cookie、跳转等同步代码,还没等React完成状态更新渲染Spinner,就已经执行了setLoading(false),甚至触发页面跳转,导致Spinner根本没机会显示。 - 主线程阻塞:
encryptdata是同步加密操作,如果数据量较大,会阻塞主线程,导致浏览器无法及时响应状态更新和渲染Spinner,同时拖慢跳转速度。 - 冗余的状态更新:
router.push之后页面会立即跳转,当前FerryCards组件会被卸载,此时调用setLoading(false)完全没有意义,组件已经不存在了。
修复方案
调整BookingHandler的代码逻辑,让React先完成Loading状态的更新,再执行耗时操作,同时移除冗余的状态更新:
const BookingHandler = (data: any) => { setLoading(true); // 用setTimeout让React优先完成状态更新,渲染Spinner setTimeout(() => { const bookingSessing = { from: parameters?.from, where: parameters?.where, travel_date: item?.travel_date, arrival_time: item?.arrival_time, departure_time: item?.departure_time, passengers: parameters?.passengers, shipper: data?.shipper, seatclass: data?.class, ship_id: data?.ship_id, class_id: data?.class_id, schedule_id: data?.schedule_id, price: data?.price, token: token, }; const encryptBookingSession = encryptdata(JSON.stringify(bookingSessing)); setCookie('bookingSession', encryptBookingSession); router.push('/booking'); // 跳转后组件卸载,无需再设置Loading为false }, 0); };
额外优化建议
- 如果
encryptdata确实耗时,建议把它改成异步函数(比如使用Web Crypto API的异步方法),避免阻塞主线程,进一步提升响应速度。 - 检查
Loading状态的变量名是否和useState声明的一致(比如是否是const [Loading, setLoading] = useState(false);,大小写要匹配)。
内容的提问来源于stack exchange,提问作者nehat khan
相关产品推荐
相关产品推荐

