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

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内

问题原因与解决方案

核心问题分析

  1. React状态更新异步特性:setLoading(true)是异步操作,调用后React不会立即更新组件状态。你紧接着执行加密、存Cookie、跳转等同步代码,还没等React完成状态更新渲染Spinner,就已经执行了setLoading(false),甚至触发页面跳转,导致Spinner根本没机会显示。
  2. 主线程阻塞:encryptdata是同步加密操作,如果数据量较大,会阻塞主线程,导致浏览器无法及时响应状态更新和渲染Spinner,同时拖慢跳转速度。
  3. 冗余的状态更新: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 22:00:14