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手动维护加载状态,步骤如下:
- 添加
isLoading状态变量,初始值为false - 在
handleSubmit发起登录请求前,将isLoading设为true - 在请求完成(无论成功或捕获到错误)后,将
isLoading设为false - 给登录按钮添加
disabled属性,避免用户重复提交请求 - 根据
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
相关产品推荐
相关产品推荐

