Skip to content

Instantly share code, notes, and snippets.

@amitasaurus
Last active May 22, 2024 10:31
Show Gist options
  • Save amitasaurus/4a2f186847455fe74467709ca4d869b8 to your computer and use it in GitHub Desktop.
Save amitasaurus/4a2f186847455fe74467709ca4d869b8 to your computer and use it in GitHub Desktop.
Form Validation with RHF & Zod
//a dynamic version where you can send schema from backend via API and form is rendered on frontend
import { useId } from 'react';
import { FormSchema } from './form';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
type Props = {};
type FormField = Array<Record<string, string | undefined>>;
type ShapeType = typeof FormSchema.shape;
type Keys = keyof ShapeType;
type ShapeField =
| z.ZodString
| z.ZodEffects<z.ZodNumber, number, number>
| z.ZodNumber;
type IFormInput = z.infer<typeof FormSchema>;
function getSchemaDetails(shape: ShapeType): FormField {
const results: FormField = [];
function getFieldType(shapeField: ShapeField): string | undefined {
if (shapeField instanceof z.ZodString) {
return 'string';
} else if (shapeField instanceof z.ZodNumber) {
return 'number';
} else if (shapeField instanceof z.ZodEffects) {
return getFieldType(shapeField._def.schema);
}
}
for (const key in shape) {
if (Object.prototype.hasOwnProperty.call(shape, key)) {
results.push({
key: key,
value: getFieldType(shape[key as Keys]),
});
}
}
return results;
}
const formData = getSchemaDetails(FormSchema.shape);
export default function DynamicForm({}: Props) {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<IFormInput>({
resolver: zodResolver(FormSchema),
});
const onSubmit = (data: IFormInput) => {
console.log(data);
};
return (
<div>
<form>
{formData.map((e) => (
<>
<div
className="border-2 border-solid rounded flex items-center mb-4"
key={useId()}
>
<input
{...register(e.key as Keys, {
valueAsNumber: e.value === 'number',
})}
type={e.value}
placeholder={e.key}
className="text-gray-700 h-10 py-1 pr-3 w-full px-2"
/>
</div>
{errors[e.key as Keys]?.message && (
<p className="text-red-700 mb-4">
{errors[e.key as Keys]?.message}
</p>
)}
</>
))}
<div className="text-center mt-6 md:mt-12">
<button
onClick={handleSubmit(onSubmit)}
className="bg-indigo-600 hover:bg-indigo-700 text-white text-xl py-2 px-4 md:px-6 rounded transition-colors duration-300"
>
Sign Up <span className="far fa-paper-plane ml-2"></span>
</button>
</div>
</form>
</div>
);
}
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
export const FormSchema = z.object({
username: z
.string()
.min(3, 'Username must not be lesser than 3 characters')
.max(25, 'Username must not be greater than 25 characters')
.regex(
/^[a-zA-Z0-9_]+$/,
'The username must contain only letters, numbers and underscore (_)'
),
email: z.string().email('Invalid email. Email must be a valid email address'),
password: z
.string()
.min(3, 'Password must not be lesser than 3 characters')
.max(16, 'Password must not be greater than 16 characters'),
fullName: z.string().min(3, 'Name must not be lesser than 3 characters'),
age: z.number().refine(
(age) => {
return Number(age) >= 18;
},
{ message: 'You must be 18 years or older' }
),
});
type IFormInput = z.infer<typeof FormSchema>;
export default function Form() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<IFormInput>({
resolver: zodResolver(FormSchema),
});
const onSubmit = (data: IFormInput) => {
console.log(data);
};
return (
<div>
<div className="mx-auto">
<div className="box bg-white p-6 md:px-12 md:pt-12 border-t-10 border-solid border-indigo-600">
<h2 className="text-3xl text-gray-800 text-center">
Create Your Account
</h2>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="signup-form mt-6 md:mt-12">
<div className="border-2 border-solid rounded flex items-center mb-4">
<input
{...register('username')}
type="text"
placeholder="Username"
className="text-gray-700 h-10 py-1 pr-3 w-full px-2"
autoComplete="username"
/>
</div>
{errors?.username?.message && (
<p className="text-red-700 mb-4">{errors.username.message}</p>
)}
<div className="border-2 border-solid rounded flex items-center mb-4">
<input
{...register('email')}
type="text"
placeholder="E-mail"
className="text-gray-700 h-10 py-1 pr-3 w-full px-2"
autoComplete="email"
/>
</div>
{errors?.email?.message && (
<p className="text-red-700 mb-4">{errors.email.message}</p>
)}
<div className="border-2 border-solid rounded flex items-center mb-4">
<input
{...register('password')}
type="password"
placeholder="Password"
className="text-gray-700 h-10 py-1 pr-3 w-full px-2"
autoComplete="current-password webauthn"
/>
</div>
{errors?.password?.message && (
<p className="text-red-700 mb-4">{errors.password.message}</p>
)}
<div className="border-2 border-solid rounded flex items-center mb-4">
<input
{...register('fullName')}
type="text"
placeholder="Full name"
className="text-gray-700 h-10 py-1 pr-3 w-full px-2"
autoComplete="name"
/>
</div>
{errors?.fullName?.message && (
<p className="text-red-700 mb-4">{errors.fullName.message}</p>
)}
<div className="border-2 border-solid rounded flex items-center mb-4">
<input
{...register('age', { valueAsNumber: true })}
type="number"
placeholder="Age"
className="text-gray-700 h-10 py-1 pr-3 w-full px-2"
/>
</div>
{errors?.age?.message && (
<p className="text-red-700 mb-4">{errors.age.message}</p>
)}
<p className="text-sm text-center mt-6">
By signing up, you agree to our{' '}
<a href="#" className="text-indigo-600 hover:underline">
Terms
</a>{' '}
and{' '}
<a href="#" className="text-indigo-600 hover:underline">
Privacy Policy
</a>
</p>
<div className="text-center mt-6 md:mt-12">
<button
onClick={handleSubmit(onSubmit)}
className="bg-indigo-600 hover:bg-indigo-700 text-white text-xl py-2 px-4 md:px-6 rounded transition-colors duration-300"
>
Sign Up <span className="far fa-paper-plane ml-2"></span>
</button>
</div>
</div>
</form>
<div className="border-t border-solid mt-6 md:mt-12 pt-4">
<p className="text-gray-500 text-center">
Already have an account,{' '}
<a href="#" className="text-indigo-600 hover:underline">
Sign In
</a>
</p>
</div>
</div>
</div>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment