I have a sign in form that is calling a server action and I'm using useActionState to handle the state of the form but I am getting a typescript error
Argument of type '(state: SignInFormInitialState, formData: FormData) => Promise<SignInFormInitialState>' is not assignable to parameter of type '(state: SignInFormInitialState) => SignInFormInitialState | Promise<SignInFormInitialState>'.
Target signature provides too few arguments. Expected 2 or more, but got 1.ts(2345)
(alias) function signIn(state: SignInFormInitialState, formData: FormData): Promise<SignInFormInitialState>
import signIn
Here is how my code is structured.
My sign in form
"use client";
import signIn from "@/actions/auth/sign-in/sign-in";
import {
ChangeEvent,
useActionState,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import CustomInput from "../custom-input";
import CustomButton from "../custom-button";
import { SignInFormInitialState } from "@/actions/auth/sign-in/interface";
import toast from "react-hot-toast";
import { useSearchParams, useRouter, usePathname } from "next/navigation";
import {
Dialog,
DialogBackdrop,
DialogPanel,
DialogTitle,
} from "@headlessui/react";
const initialState: SignInFormInitialState = {
username: "",
password: "",
};
interface SignInFormProps {
login_required: string;
}
export default function SignInForm({ login_required }: SignInFormProps) {
const [state, formAction, pending] = useActionState<SignInFormInitialState>(
signIn,
initialState
);
const searchParams = useSearchParams();
const { replace } = useRouter();
const pathname = usePathname();
const toastShown = useRef(false);
const [passwordLength, setPasswordLength] = useState<number>(0);
const usernameRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
const handlePasswordLengthChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
setPasswordLength(e.target.value.length);
},
[]
);
useEffect(() => {
if (login_required && !toastShown.current) {
toast.error("Kindly Sign In!");
toastShown.current = true;
const params = new URLSearchParams(searchParams);
params.delete("login_required");
replace(`${pathname}`);
}
}, [pathname, replace, searchParams, login_required]);
// Focus on input on load
useEffect(() => {
if (usernameRef.current?.value === "") {
usernameRef.current.focus();
} else if (
passwordRef.current?.value === "" ||
(passwordRef.current?.value && passwordRef.current?.value?.length < 8)
) {
passwordRef.current.focus();
} else {
// do nothing
}
}, [state?.errors?.username, state?.errors?.password]);
return (
<form action={formAction} className="flex flex-col gap-4">
<CustomInput
label="Username"
type="text"
name="username"
defaultValue={state.username}
pending={pending}
ref={usernameRef}
isLabelError={
state?.errors?.username && state?.errors?.username.length > 0
}
errors={state?.errors}
errorsName={state?.errors?.username}
/>
<CustomInput
label="Password"
type="password"
name="password"
defaultValue={state.password}
pending={pending}
ref={passwordRef}
isLabelError={
(state?.errors?.password && state?.errors?.password.length > 0) ||
(state?.errors?.authStatus && state?.errors?.authStatus.length > 0)
}
errors={state?.errors}
errorsName={state?.errors?.authStatus || state?.errors?.password}
passwordLength={passwordLength}
onChange={handlePasswordLengthChange}
/>
<CustomButton
type="submit"
pending={pending}
title="Sign In"
pendingTitle="Signing In..."
/>
</form>
);
}
My server action
"use server";
import { redirect } from "next/navigation";
import { z } from "zod";
import { cookies } from "next/headers";
import { encrypt } from "@/lib/encrypt";
import { PrismaClient } from "@prisma/client";
import { SignInFormInitialState } from "./interface";
const prisma = new PrismaClient();
const signInSchema = z.object({
username: z.string().min(1, "Username is required"),
password: z.string().min(8, "Password must be at lease 8 characters long"),
});
export default async function signIn(
state: SignInFormInitialState,
formData: FormData
): Promise<SignInFormInitialState> {
const username = String(formData.get("username"));
const password = String(formData.get("password"));
console.log(state);
const validatedFields = signInSchema.safeParse({
username,
password,
});
if (!validatedFields.success) {
return {
username,
password,
errors: validatedFields?.error?.flatten()?.fieldErrors,
};
}
const user = await prisma.user.findUnique({
where: { username: username },
});
if (!user) {
return {
username,
password,
errors: {
authStatus: ["User does not exist"],
},
};
}
const response = await fetch("http://localhost:3000/api/auth/sign-in", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, password }),
});
if (response.status === 401) {
return {
username,
password,
errors: {
authStatus: ["Invalid Username or Password"],
},
};
}
if (response.status === 500) {
throw new Error("Something went wrong");
}
const result = await response.json();
const expires = new Date(Date.now() + 3 * 60 * 1000);
const session = await encrypt({ result, expires });
const cookieStore = await cookies();
cookieStore.set("session", session, {
expires,
httpOnly: true,
secure: process.env.NODE_ENV === "production",
path: "/",
});
redirect("/dashboard/policies");
}
And here is my interface
export interface SignInFormErrors {
username?: string[];
password?: string[];
authStatus?: string[];
}
export interface SignInFormInitialState {
username: string;
password: string;
errors?: SignInFormErrors;
}
And this is my package.json file
"dependencies": {
"@headlessui/react": "^2.2.0",
"@prisma/client": "^6.0.1",
"jose": "^5.9.6",
"next": "^15.0.3",
"next-nprogress-bar": "^2.3.15",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hot-toast": "^2.4.1",
"react-icons": "^5.4.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.9",
"@types/node": "^22.10.1",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.1",
"eslint": "^9.16.0",
"eslint-config-next": "^15.0.4",
"postcss": "^8.4.49",
"prisma": "^6.0.1"
}
When I remove the formData params from the server function, the warning goes away but the formData is required for me to get access to whatever was submitted.
I need assistance on how i can make this error go away.
you should add the second generic parameter in your useActionState
declaration to fix typescript error:
const [state, formAction, pending] = useActionState<SignInFormInitialState, FormData>(
signIn,
initialState
);