Create field
All inputs should be included in createField
, which receives key-value pairs,
with the key being the component name
and the value being the React element
.
Prepare components
You can skip this section if you have already input components.
Imagine, these are some of the input components in your projects
import React from "react";
export interface TextInputProps
extends React.ComponentPropsWithoutRef<"input"> {}
const TextInput = React.forwardRef<React.ElementRef<"input">, TextInputProps>(
(props, ref) => {
return <input {...props} ref={ref} type="text" />;
}
);
export default TextInput;
import React from "react";
export interface NumberInputProps
extends React.ComponentPropsWithoutRef<"input"> {}
const NumberInput = React.forwardRef<
React.ElementRef<"input">,
NumberInputProps
>((props, ref) => {
return <input {...props} ref={ref} type="number" />;
});
export default NumberInput;
import React from "react";
export interface SelectInputProps
extends React.ComponentPropsWithoutRef<"select"> {
options: { label: string; value: any }[];
}
const SelectInput = React.forwardRef<
React.ElementRef<"select">,
SelectInputProps
>(({ options = [], ...props }, ref) => {
return (
<select {...props} ref={ref}>
{options.map((option, index) => (
<option key={index} value={option.value}>
{option.label}
</option>
))}
</select>
);
});
export default SelectInput;
Make field
Now, this is the main section to reuse your form input components in all your projects without
re-importing those components and defining register(...)
repeatedly.
To define it easily, just call createField
and add them into createField with key-value pairs.
createField
will automatically infer types when you use the <Field />
in other places
import React from "react";
import { createField } from "hookform-field";
import TextInput from "@/components/ui/input";
import NumberInput from "@/components/ui/number";
import Select from "@/components/ui/select";
const Field = createField({
text: TextInput,
number: NumberInput,
select: Select,
});
export default Field;
Additionally, you can still apply next/dynamic
or React.lazy
to get chunk files and easily apply fallback for Suspense.
import React from "react";
import { createField } from "hookform-field";
const TextInput = React.lazy(() => import("@/components/ui/input"));
const NumberInput = React.lazy(() => import("@/components/ui/number"));
const Select = React.lazy(() => import("@/components/ui/select"));
// For next.js
// const TextInput = dynamic(() => import("@/components/ui/input"), {
// ssr: true,
// loading: () => <div>Loading</div>,
// });
// const NumberInput = dynamic(() => import("@/components/ui/number"), {
// ssr: true,
// loading: () => <div>Loading</div>,
// });
// const Select = dynamic(() => import("@/components/ui/select"), {
// ssr: true,
// loading: () => <div>Loading</div>,
// });
const Field = createField({
text: TextInput,
number: NumberInput,
select: Select,
});
export default Field;
Finally, just use them like this, and you will get suggested props based on the component you select.
import React from "react";
import { Form } from "hookform-field";
import Field from "@/components/form/field";
export interface CreateUserPageProps {}
const CreateUserPage = (props: CreateUserPageProps) => {
return (
<Form className="dark:bg-neutral-900 p-5 rounded-lg mt-6">
<Field
name="firstName"
label="First name"
component="text" // "text" | "number" | "select" type created
placeholder="Enter first name"
/>
<Field
name="lastName"
label="last name"
component="text" // "text" | "number" | "select" type created
placeholder="Enter last name"
/>
<Field
name="age"
label="Age"
component="select" // "text" | "number" | "select" type created
options={[
// type-safe, as TypeScript will require you to render the options prop because it is required
{ label: "Option 1", value: "option-1" },
{ label: "Option 2", value: "option-2" },
]}
placeholder="Select age"
/>
</Form>
);
};
export default CreateUserPage;
Classnames
You can add class names for custom element styling as follows:
const Field = createField(
{
...
},
{
classNames: {
root: "flex flex-col",
input: "h-10 px-4 rounded-md",
message: "text-sm",
description: "text-sm",
label: "text-sm leading-normal mb-1",
},
}
);
Component Type
You can add class names for custom element styling as follows:
const Field = createField(
{
...
},
{
components: {
description: "small",
label: "label",
message: "small",
root: "div"
}
}
);
Result
Tada, perfectly. You will get result like this.