ui icon indicating copy to clipboard operation
ui copied to clipboard

Properly divide Form components into reusable subcomponents

Open DownDev opened this issue 1 year ago • 2 comments

"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?

DownDev avatar Feb 18 '24 14:02 DownDev

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?

LeonardoVini avatar Mar 27 '24 20:03 LeonardoVini

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.

I0T-4-L1F3 avatar May 16 '24 08:05 I0T-4-L1F3

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.

shadcn avatar Jun 25 '24 23:06 shadcn

I am interested to know more.

rahulkrishnakumar avatar Oct 02 '24 17:10 rahulkrishnakumar

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>
      )}
    />
  );
};

steege252 avatar Oct 24 '24 09:10 steege252