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

Next.js 13中Clerk自定义登录流如何实现isLoading状态?

在Clerk自定义登录流程中实现加载状态(Next.js 13)

问题描述

我在Next.js 13中使用Clerk构建认证功能,采用自定义流程结合useSignIn与useSignUp。所有配置已完成,但无法在用户登录时显示加载spinner。查阅Clerk文档后发现官方未提供isLoading状态用于检测登录中状态,请问该如何实现?

原登录页面代码

"use client"

import { useState } from "react";
import { useSignIn, SignIn } from "@clerk/clerk-react"
import { useRouter } from "next/navigation";
import { FaGoogle } from "react-icons/fa6"
import Link from "next/link"

export default function SignInPage() {
  const { isLoaded, signIn, setActive } = useSignIn();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState(null);
  const router = useRouter();

  const handleSubmit = async (e: React.FormEvent<HTMLButtonElement>) => {
    e.preventDefault();

    if(!isLoaded) {
      return;
    }

    try {
      const result = await signIn.create({
        identifier: email,
        password,
      });

      if (result.status === "complete") {
        setError(null)
        console.log(result);
        await setActive({ session: result.createdSessionId });
        router.push("/")
      }
      else {
        /*Investigate why the login hasn't completed */
        console.log(result);
      }

    } catch (err: any) {
      console.error(JSON.stringify(err, null, 2))
      setError(err?.errors[0].message)
    }
  }

  return (
    <div className="w-100 h-screen flex flex-col justify-center items-center bg-white">
      <div className="w-full max-w-[360px] p-3 flex flex-col gap-5 items-center">
        <h2 className="text-2xl font-medium">Sign In</h2>       
        <form className="w-full flex flex-col gap-2">
          <div className="flex flex-col gap-1">
            <input className="text-sm p-2 h-[36px] font-normal bg-input-background shadow-[inset_0_0_0_1px_rgba(15,15,15,0.1)] rounded-md" type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Enter your email address" />
          </div>
          <div className="flex flex-col gap-1">
            <input className="text-sm p-2 h-[36px] font-normal bg-input-background shadow-[inset_0_0_0_1px_rgba(15,15,15,0.1)] rounded-md" type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Enter password" />
          </div>
          {error &&         
            <div className="w-full flex items-center justify-center">
              <p className="text-xs text-sign-in">{error}</p>
            </div>
          }
          <button className="text-sm w-full p-2 h-[36px] flex items-center justify-center bg-sign-in-background text-sign-in shadow-[inset_0_0_0_1px_rgba(235,87,87,0.3)] rounded-md" type="submit" onClick={handleSubmit}>
            Sign In
          </button>
          <div className="relative mt-4 border-t border-light-gray">
            <span className="text-xs bg-white text-light-gray px-2 absolute top-[-12.5px] left-[50%] translate-x-[-50%] ">or</span>
            <button className="mt-6 w-full p-4 h-[36px] text-xs bg-light-gray flex gap-2 items-center justify-center shadow-[inset_0_0_0_1px_rgba(15,15,15,0.1)] rounded-md hover:bg-hover-light-gray transition-all">
              <FaGoogle color="gray" />
              Sign in with Google
            </button>
          </div>
        </form>
        <div className="flex mt-2 items-center justify-center">
          <p className="text-xs text-dark-gray">No account? <Link href="/sign-up" className="text-slate cursor-pointer">Sign Up</Link></p>
        </div>
      </div>
    </div>
  )
}

解决方案

通过React的useState手动维护加载状态,步骤如下:

  1. 添加isLoading状态变量,初始值为false
  2. 在handleSubmit发起登录请求前,将isLoading设为true
  3. 在请求完成(无论成功或捕获到错误)后,将isLoading设为false
  4. 给登录按钮添加disabled属性,避免用户重复提交请求
  5. 根据isLoading状态切换按钮显示内容(加载中显示spinner/提示文字,否则显示"Sign In")

修改后的完整代码

"use client"

import { useState } from "react";
import { useSignIn, SignIn } from "@clerk/clerk-react"
import { useRouter } from "next/navigation";
import { FaGoogle } from "react-icons/fa6"
import Link from "next/link"

export default function SignInPage() {
  const { isLoaded, signIn, setActive } = useSignIn();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState(null);
  // 新增加载状态管理
  const [isLoading, setIsLoading] = useState(false);
  const router = useRouter();

  const handleSubmit = async (e: React.FormEvent<HTMLButtonElement>) => {
    e.preventDefault();

    if(!isLoaded || isLoading) {
      return;
    }

    try {
      // 发起请求前设置加载状态为true
      setIsLoading(true);
      const result = await signIn.create({
        identifier: email,
        password,
      });

      if (result.status === "complete") {
        setError(null)
        await setActive({ session: result.createdSessionId });
        router.push("/")
      }
      else {
        console.log("登录未完成:", result);
      }

    } catch (err: any) {
      console.error("登录错误:", JSON.stringify(err, null, 2))
      setError(err?.errors[0].message)
    } finally {
      // 请求完成后无论成功失败,都关闭加载状态
      setIsLoading(false);
    }
  }

  return (
    <div className="w-100 h-screen flex flex-col justify-center items-center bg-white">
      <div className="w-full max-w-[360px] p-3 flex flex-col gap-5 items-center">
        <h2 className="text-2xl font-medium">Sign In</h2>       
        <form className="w-full flex flex-col gap-2">
          <div className="flex flex-col gap-1">
            <input className="text-sm p-2 h-[36px] font-normal bg-input-background shadow-[inset_0_0_0_1px_rgba(15,15,15,0.1)] rounded-md" type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Enter your email address" disabled={isLoading} />
          </div>
          <div className="flex flex-col gap-1">
            <input className="text-sm p-2 h-[36px] font-normal bg-input-background shadow-[inset_0_0_0_1px_rgba(15,15,15,0.1)] rounded-md" type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Enter password" disabled={isLoading} />
          </div>
          {error &&         
            <div className="w-full flex items-center justify-center">
              <p className="text-xs text-sign-in">{error}</p>
            </div>
          }
          <button 
            className="text-sm w-full p-2 h-[36px] flex items-center justify-center bg-sign-in-background text-sign-in shadow-[inset_0_0_0_1px_rgba(235,87,87,0.3)] rounded-md opacity-80" 
            type="submit" 
            onClick={handleSubmit}
            disabled={isLoading}
          >
            {/* 根据加载状态切换显示内容,这里用文字示例,可替换为自定义spinner组件 */}
            {isLoading ? "Loading..." : "Sign In"}
          </button>
          <div className="relative mt-4 border-t border-light-gray">
            <span className="text-xs bg-white text-light-gray px-2 absolute top-[-12.5px] left-[50%] translate-x-[-50%] ">or</span>
            <button className="mt-6 w-full p-4 h-[36px] text-xs bg-light-gray flex gap-2 items-center justify-center shadow-[inset_0_0_0_1px_rgba(15,15,15,0.1)] rounded-md hover:bg-hover-light-gray transition-all" disabled={isLoading}>
              <FaGoogle color="gray" />
              {isLoading ? "Loading..." : "Sign in with Google"}
            </button>
          </div>
        </form>
        <div className="flex mt-2 items-center justify-center">
          <p className="text-xs text-dark-gray">No account? <Link href="/sign-up" className="text-slate cursor-pointer">Sign Up</Link></p>
        </div>
      </div>
    </div>
  )
}

额外优化说明

  • 给输入框也添加了disabled属性,避免用户在加载中修改输入内容
  • 使用finally块确保加载状态一定会被重置,防止因错误导致状态卡住
  • 按钮添加了opacity-80类(可根据你的Tailwind配置调整),让禁用状态视觉上更明显

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 06:00:02