In my Next.js 13 project, I have a login form as shown below:
"use client";
import * as React from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { signIn } from "next-auth/react";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { useSearchParams } from "next/navigation";
import Link from "next/link";
import { cn } from "@/lib/util";
import { userAuthSchema } from "@/lib/validations/auth";
import { buttonVariants } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Icons } from "@/components/icons";
import { loginUser } from "@/lib/login-user";
export type FormData = z.infer<typeof userAuthSchema>;
export function LoginForm() {
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm<FormData>({ resolver: zodResolver(userAuthSchema) });
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [error, setError] = React.useState<string | null>(null);
const [isGoogleLoading, setIsGoogleLoading] = React.useState<boolean>(false);
const searchParams = useSearchParams();
async function onSubmit(submittedData: FormData) {
await loginUser({
submittedData,
setError,
setIsLoading,
reset,
searchParams,
});
}
return (
<div className="grid gap-6">
{error && (
<p className="text-sm text-red-500 animate-in fade-in-0 slide-in-from-left-1">
{error}
</p>
)}
<form onSubmit={handleSubmit(onSubmit)}>
<div className="grid gap-6">
<div className="grid gap-1">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="[email protected]"
autoCapitalize="none"
autoComplete="email"
autoCorrect="off"
disabled={isLoading || isGoogleLoading}
{...register("email")}
/>
{errors.email && (
<p className="px-1 text-xs text-red-600 animate-in fade-in-0 slide-in-from-left-1">
{errors.email.message}
</p>
)}
</div>
<div className="grid gap-1">
<div className="flex items-center justify-between">
<Label htmlFor="password">Password</Label>
<Link
href="/request-password-reset"
className="text-sm font-medium text-sky-700 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-400 focus-visible:ring-offset-2 active:text-sky-400"
>
Forgot password?
</Link>
</div>
<Input
id="password"
type="password"
autoCapitalize="none"
autoComplete="off"
autoCorrect="off"
disabled={isLoading || isGoogleLoading}
{...register("password")}
/>
{errors.password && (
<p className="px-1 text-xs text-red-600 animate-in fade-in-0 slide-in-from-left-1">
{errors.password.message}
</p>
)}
</div>
<button className={cn(buttonVariants())} disabled={isLoading}>
{isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
Login
</button>
</div>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-neutral-200" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-neutral-100 px-2">Or</span>
</div>
</div>
<button
type="button"
className={cn(buttonVariants({ variant: "outline" }), "w-full")}
onClick={() => {
setIsGoogleLoading(true);
signIn("google", {
callbackUrl: searchParams?.get("from") || "/blog",
});
}}
disabled={isLoading || isGoogleLoading}
>
{isGoogleLoading ? (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
) : (
<Icons.google className="mr-2 h-6 w-6" />
)}{" "}
Login with Google
</button>
</div>
);
}
As you can see, searchParams
is one of the arguments passed to the loginUser()
function inside the onSubmit
handler. Here's the relevant portion of the code:
async function onSubmit(submittedData: FormData) {
await loginUser({
submittedData,
setError,
setIsLoading,
reset,
searchParams,
});
}
And here's the loginUser()
function:
import type { FormData } from "@/components/auth/register-form";
import { signIn } from "next-auth/react";
const RESPONSE_MESSAGES = {
USER_NOT_FOUND: "User not found",
INVALID_PASSWORD: "Invalid password",
LOGIN_SUCCESSFUL: "Logged in successfully",
LOGIN_FAILURE: "Login failed. Please try again.",
EMAIL_NOT_VERIFIED: "Please verify your email",
};
interface LoginUserProps {
submittedData: FormData;
setError: React.Dispatch<React.SetStateAction<string | null>>;
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
reset: () => void;
searchParams: any;
}
export async function loginUser({
submittedData,
setError,
setIsLoading,
reset,
searchParams,
}: LoginUserProps) {
setIsLoading(true);
setError(null);
try {
const response = await fetch("/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(submittedData),
});
const responseData = await response.json();
if (!response.ok) {
throw new Error(RESPONSE_MESSAGES.LOGIN_FAILURE);
}
if (responseData.message === RESPONSE_MESSAGES.USER_NOT_FOUND) {
setError(RESPONSE_MESSAGES.USER_NOT_FOUND);
return;
}
if (responseData.message === RESPONSE_MESSAGES.EMAIL_NOT_VERIFIED) {
setError(RESPONSE_MESSAGES.EMAIL_NOT_VERIFIED);
return;
}
if (responseData.message === RESPONSE_MESSAGES.INVALID_PASSWORD) {
setError(RESPONSE_MESSAGES.INVALID_PASSWORD);
return;
}
// Use signIn just to log in, and use router.push for redirection.
if (responseData.message === RESPONSE_MESSAGES.LOGIN_SUCCESSFUL) {
reset(); // Reset the form state
// Sign-in and let it handle the redirection
signIn("credentials", {
...submittedData,
callbackUrl: searchParams?.get("from") || "/blog",
});
}
} catch (error) {
setError((error as Error).message);
} finally {
setIsLoading(false);
}
}
As you can see, I have currently typed searchParams
as any
. Of course, I don't want to do that. Here is the relevant portion of the code:
interface LoginUserProps {
submittedData: FormData;
setError: React.Dispatch<React.SetStateAction<string | null>>;
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
reset: () => void;
searchParams: any;
}
Can someone tell me what would be the type of searchParams
? Thanks...
The return type of useSearchParmas
is ReadonlyURLSearchParams
in the App dir and ReadonlyURLSearchParams | null
in the pages directory.
The ReadonlyURLSearchParams
is imported from next/navigation
import { ReadonlyURLSearchParams } from "next/navigation"
// ...
const searchParams = useSearchParams();
// ?^ searchParams: ReadonlyURLSearchParams