ui
ui copied to clipboard
Properly divide Form components into reusable subcomponents
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Textarea } from "./ui/textarea"
const FormSchema = z.object({
welcomeChannel: z.string(),
welcomeMessage: z.string().min(2),
})
export default function WelcomeForm() {
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
})
function onSubmit(data: z.infer<typeof FormSchema>) {
console.log(data)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="welcomeChannel"
render={({ field }) => (
<FormItem>
<FormLabel>Welcome channel</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a channel" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="123">Channel 1</SelectItem>
<SelectItem value="234">Channel 2</SelectItem>
<SelectItem value="345">Channel 3</SelectItem>
</SelectContent>
</Select>
<FormDescription>
The channel where the welcome message will be sent
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="welcomeMessage"
render={({ field }) => (
<FormItem>
<FormLabel>Welcome message</FormLabel>
<FormControl>
<Textarea
placeholder="Message"
className="resize-none"
{...field}
/>
</FormControl>
<FormDescription>
The message that will be sent to the welcome channel
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Save</Button>
</form>
</Form>
)
}
lets say i want to extract the welcomeChannel Select component into separate file cause i want to reuse it in other forms
how would i do that?
would i take out the whole FormField tag and pass form as props so i can set the control={form.control} or would i rather take out the FormItem and pass in as props the field given in render, also how would i type annotate it?
What if I want to make it reusable for other forms within my application with different select options, would it be possible? Would I send the field name, placeholder and other information as properties?
The <Form> component is a form provider, so can I use the context form in this case?
I'm also interested in this matter! I'm new to React and I'm glad that shadcn exists, but I'm having trouble figuring out how to make it reusable. Duplicating code isn't ideal.
This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.
I am interested to know more.
to anyone else struggling with this: Apparently, you can just use useFormContext to gain access to the form from useForm in your subcomponent. example from above:
export default function WelcomeForm() {
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
});
function onSubmit(data: z.infer<typeof FormSchema>) {
console.log(data);
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<WelcomeChannel />
...
</form>
</Form>
);
}
export const WelcomeChannel = () => {
const form = useFormContext();
return (
<FormField
control={form.control}
name="welcomeChannel"
render={({ field }) => (
<FormItem>
<FormLabel>Welcome channel</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a channel" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="123">Channel 1</SelectItem>
<SelectItem value="234">Channel 2</SelectItem>
<SelectItem value="345">Channel 3</SelectItem>
</SelectContent>
</Select>
<FormDescription>
The channel where the welcome message will be sent
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
);
};