I’m fairly new to using Typescript and I’m having a go at setting up Auth in my first ts React project. I’ve successfully set up validation using react-hook-form
in an AuthForm.tsx
component that renders the Login form and Sign Up form interchangeably.
I’ve set it up to map over input field data for each form that I’ve stored as objects in a separate constants.ts
.
Currently, when I try to access the errorMessages[name]
in the Sign Up iteration of the form I’m getting an error stating that the errors have been implicitly typed any due to a FieldError conflict.
I’ve been poking around the react-hook-form
documentation on FieldErrors
and am feeling a bit out of my depth. Has anyone had a similar issue building a reusable form component in Typescript?
Error:
Element implicitly has an 'any' type because expression of type
'"username" | "password" | "confirm"' can't be used to index type 'FieldErrors<Inputs>'.
Property 'confirm' does not exist on type 'FieldErrors<Inputs>'.
AuthForm.tsx
import H1 from "./H1";
import { loginInputFieldData, signupInputFieldData } from "../lib/constants";
import { useAuthFormToggle } from "../lib/hooks";
import { SubmitHandler, useForm } from "react-hook-form";
import { LoginFormInputs, SignUpFormInputs } from "../lib/types";
import { hasRegexErrorMessage } from "../lib/utils";
type Inputs = LoginFormInputs | SignUpFormInputs;
export default function AuthForm() {
const { formType, handleAuthFormToggle } = useAuthFormToggle();
const {
register,
watch,
handleSubmit,
formState: { errors },
} = useForm<Inputs>();
const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data);
return (
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col space-y-4 items-center mx-2"
>
<H1 className="text-center text-lg">
{formType === "login" ? "Log In" : "Sign Up"}
</H1>
{formType === "login"
? loginInputFieldData.map(
({ type, placeholder, name, errorMessages }) => (
<div key={name}>
<input
{...register(name, { required: errorMessages.required })}
type={type}
placeholder={placeholder}
className="bg-secondary h-9 border border-primary border-opacity-20 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-45 px-2"
/>
{errors[name] && (
<p className="text-red-500">{errors[name]?.message}</p>
)}
</div>
)
)
: signupInputFieldData.map(
({ type, placeholder, name, errorMessages }) => (
<div>
<input
{...register(name as keyof SignUpFormInputs, {
required: errorMessages.required,
pattern: hasRegexErrorMessage(errorMessages)
? {
value:
/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/,
message: errorMessages.regEx,
}
: undefined,
validate: (val: string) => {
if (watch("password") != val) {
return errorMessages.mismatch;
}
},
})}
type={type}
placeholder={placeholder}
className="bg-secondary h-9 border border-primary border-opacity-20 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-45 px-2"
/>
{errors[name] && (
<p className="text-sm text-red-500">
{errors[name]?.message}
</p>
)}
</div>
)
)}
<input
type="submit"
className="bg-primary text-white rounded-md px-5 py-2 hover:bg-blue-800 transition duration-75"
/>
<hr className="w-full border-t border-gray-300 opacity-65" />
<p className="text-xs opacity-50">
{formType === "login" ? "Or" : "Already signed up?"}{" "}
<span
onClick={handleAuthFormToggle}
className="text-bold underline cursor-pointer hover:opacity-75"
>
{formType === "login" ? "create an account" : "Login"}
</span>
</p>
</form>
);
}
constants.ts
type TLoginInputFieldData = {
type: string;
placeholder: "Username" | "Password";
name: "username" | "password";
errorMessages: {
required: string;
};
};
export const loginInputFieldData: TLoginInputFieldData[] = [
{
type: "text",
placeholder: "Username",
name: "username",
errorMessages: { required: "Username is required." },
},
{
type: "password",
placeholder: "Password",
name: "password",
errorMessages: {
required: "Password is required.",
},
},
];
type TSignUpInputFieldData = {
type: string;
placeholder: "Username" | "Password" | "Confirm password";
name: "username" | "password" | "confirm";
errorMessages: {
required: string;
regEx?: string;
mismatch?: string;
};
};
export const signupInputFieldData: TSignUpInputFieldData[] = [
{
type: "text",
placeholder: "Username",
name: "username",
errorMessages: { required: "Username is required." },
},
{
type: "password",
placeholder: "Password",
name: "password",
errorMessages: {
required: "Password is required.",
regEx:
"Password must have a minimum of 8 characters.nPassword must contain one uppercase letter.nPassword must contain one lowercase letter.nPassword must contain one digit 0-9.nPassword must contain one special character.",
},
},
{
type: "password",
placeholder: "Confirm password",
name: "confirm",
errorMessages: {
required: "Password confirmation is required.",
mismatch: "Password and confirmation must match.",
},
},
];