I’m trying to create a reusable input field, but I don’t know how to avoid repeating "control={form.control}". My problem is that "form" is defined in the "PracticeForm" component (as I need to declare it for the general form), not in "ItemInput", so I get an error:
PracticeForm.tsx:
import { useForm } from "react-hook-form";
import { Button } from "./ui/button";
import { Form } from "./ui/form";
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import ItemInput from "./ItemInput";
const formSchema = z.object({
username: z.string(),
email: z.string()
.email({
message: "Please enter a valid email"
})
});
const PracticeForm = () => {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
email: "email"
},
});
function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values);
}
return (
<div>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="flex flex-col w-52 gap-2"
>
<ItemInput name="username" placeholder={"Enter username"}/>
<ItemInput name="email" placeholder={"Enter email"}/>
<Button variant={"outline"} type="submit">
Submit
</Button>
</form>
</Form>
</div>
);
};
export default PracticeForm;
ItemInput.tsx:
import { FormControl, FormField, FormItem, FormMessage } from "./ui/form"
import { Input } from "./ui/input"
type InputProps = {
name: string
placeholder: string
}
const ItemInput = ({name, placeholder}: InputProps) => {
return (
<div>
<FormField
control={form.control}
name={name}
render={({ field }) => (
<FormItem>
<FormControl>
<Input placeholder={placeholder} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
)
}
export default ItemInput
Is there any way I can avoid repeating the "control={form.control" line every time I reuse the "ItemInput" custom component?
>Solution :
To avoid repetition, pass the control prop from the parent component to ItemInput. Here’s how:
Modify your ItemInput.tsx file as follows:
import { FormControl, FormField, FormItem, FormMessage } from "./ui/form";
import { Input } from "./ui/input";
import { Control } from "react-hook-form"; // Import Control type
type InputProps = {
name: string;
placeholder: string;
control: Control; // Add control prop of type Control
};
const ItemInput = ({ name, placeholder, control }: InputProps) => {
return (
<div>
<FormField
control={control} // Use the control prop passed from the parent
name={name}
render={({ field }) => (
<FormItem>
<FormControl>
<Input placeholder={placeholder} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
);
};
export default ItemInput;
Now, in your PracticeForm.tsx, you can pass the form control to the ItemInput component as a prop named control:
<ItemInput name="username" placeholder="Enter username" control={form.control} />
<ItemInput name="email" placeholder="Enter email" control={form.control} />
To avoid repeating the control={form.control} line and ensure ItemInput component has access to control object provided by react-hook-form, do this.
A helpful tip to prevent repetition is to use this technique.
// ItemInput.tsx
import { useFormContext } from "react-hook-form"; // Import useFormContext
type InputProps = {
name: string;
placeholder: string;
};
const ItemInput = ({ name, placeholder }: InputProps) => {
const { control } = useFormContext(); // Use useFormContext to access the form control
return (
<div>
<FormField
control={control}
name={name}
render={({ field }) => (
<FormItem>
<FormControl>
<Input placeholder={placeholder} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
);
};
export default ItemInput;