Skip to content

Instantly share code, notes, and snippets.

@MrNekoShin
Created February 21, 2024 02:22
Show Gist options
  • Save MrNekoShin/0f5204764bee15e32fc6c51001a63ab6 to your computer and use it in GitHub Desktop.
Save MrNekoShin/0f5204764bee15e32fc6c51001a63ab6 to your computer and use it in GitHub Desktop.
Dynamic React Hook Form From Schema
import React, { ChangeEvent } from "react";
import { FieldError, FieldErrorsImpl, Merge, useForm } from "react-hook-form";
type ValidationSchema = {
required?:
| string
| {
value: boolean;
message: string;
};
maxLength?: {
value: number;
message: string;
};
minLength?: {
value: number;
message: string;
};
max?: {
value: number;
message: string;
};
min?: {
value: number;
message: string;
};
pattern?: {
value: RegExp;
message: string;
};
// validate?: Function | Object;
};
type SchemaField = {
label: string;
type: string;
validation: ValidationSchema;
};
type Schema = {
[key: string]: SchemaField;
};
type Data = {
[key: string]: string;
};
const DynamicForm = ({ schema }: { schema: Schema }) => {
const {
register,
handleSubmit,
formState: { errors },
setValue,
} = useForm();
const onSubmit = (data: Data) => {
console.log(data);
};
const handleInputChange = (
event: ChangeEvent<HTMLInputElement | HTMLSelectElement>,
) => {
const { name, value } = event.target;
setValue(name, value);
};
const renderContent = (
content:
| string
| FieldError
| Merge<FieldError, FieldErrorsImpl<any>>
| undefined,
): React.ReactNode => {
if (typeof content === "string") {
return content; // String can be directly assigned to ReactNode
} else if (typeof content === "object" && "message" in content) {
return (content as FieldError).message; // Rendering the error message
} else {
return null; // Undefined or other types, handle accordingly
}
};
const renderFormControl = (key: string, field: SchemaField) => {
switch (field.type) {
case "text":
case "email":
case "number":
case "password":
return (
<div key={key}>
<label>{field.label}</label>
<input
{...register(key, field.validation)}
onChange={handleInputChange}
/>
{errors[key] && <span>{renderContent(errors[key])}</span>}
</div>
);
case "select":
return (
<div key={key}>
<label>{field.label}</label>
<select
{...register(key, field.validation)}
onChange={handleInputChange}
>
{/* Options for select dropdown */}
</select>
{errors[key] && <span>{renderContent(errors[key])}</span>}
</div>
);
case "checkbox":
return (
<div key={key}>
<label>
<input
type="checkbox"
{...register(key, field.validation)}
onChange={handleInputChange}
/>
{field.label}
</label>
{errors[key] && <span>{renderContent(errors[key])}</span>}
</div>
);
default:
return null;
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* Render form controls dynamically */}
{Object.entries(schema).map(([key, field]) =>
renderFormControl(key, field),
)}
<button type="submit">Submit</button>
</form>
);
};
export default DynamicForm;
const schema = {
firstName: {
label: "First Name",
type: "text",
validation: {
required: "First Name is required",
minLength: {
value: 3,
message: "First Name should have at least 3 characters",
},
},
},
lastName: {
label: "Last Name",
type: "text",
validation: {
required: "Last Name is required",
},
},
email: {
label: "Email",
type: "email",
validation: {
required: "Email is required",
pattern: {
value: /^\S+@\S+$/i,
message: "Invalid email address",
},
},
},
};
export default schema;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment