Next.js Discord

Discord Forum

react hook form useFieldArray with shadcn dialog and card component.

Unanswered
Yellow croaker posted this in #help-forum
Open in Discord
Yellow croakerOP
Hello i'm trying to accomplish certain behavior on my application. In my app user can add multiple work experiences. and when they click add new a dialog should popup where they write their work experiences. After they save their form inputs. then the values should be listed on the page, and if they click one of the work experiences they added then it should open that information again on the dialog and then they could update that information. So in short.
-User can add multiple work experiences via dialog form.
-User can delete work experience
-Work experinces are listed after saving them
-user can modify their earlier work experiences by clicking them.

My problem is now with useFieldArray and dialog. Currently when i submit my form it just gives the values as empty workexperience
  const router = useRouter();
  const { updateSection } = useUserStore();
  const { data: resumeData, mutate } = useSWR('/api/resume');
  const store = useUserStore();
  const workExperiences = useMemo(
    () => store.user.workExperiences ?? [],
    [store.user.workExperiences],
  );

const onSubmit = async (formData: formSchemaType) => {
    console.log('working?', formData);
    mutate({ ...formData }, false);
    try {
      const dataToSend = {
        ...formData,
        resumeId: resume.id,
      };
      console.log('formData', formData.workExperience);
      console.log('datatoSend', dataToSend);
      const updatedData = await patchResume(resume.id, dataToSend);
      mutate(updatedData);
      if (!updatedData.ok) {
        return toast({
          title: 'something went wrong',
          description: 'Please try again later',
          variant: 'destructive',
        });
      }
      if (updatedData.ok) {
        updateSection('workExperiences', formData);
        mutate('/api/resume', updatedData);
      }
      router.refresh();
    } catch (error) {
      console.error(error);
    }
  };

10 Replies

Yellow croakerOP
return (
    <div>
      <Card className="mb-4">
        <CardHeader>
          <CardTitle>Work Experience</CardTitle>
        </CardHeader>
        <CardContent>
          <div className="mb-2">
            {workExperiences.map((workExperience, index) => (
              <div key={index} className="mb-2">
                <p>{workExperience.city}</p>
                <Separator className="my-2" />
              </div>
            ))}
          </div>
        </CardContent>
        <CardContent>
          <div className="mb-2"></div>
        </CardContent>
        <CardFooter></CardFooter>
        <div className="grid grid-cols-1 gap-6">
          <Dialog>
            <DialogTrigger>
              <Button>Make new resume</Button>
            </DialogTrigger>
            <DialogContent>
              <DialogHeader>
                <DialogTitle>Are you sure absolutely sure?</DialogTitle>
                <DialogDescription>
                  This action cannot be undone. This will permanently delete
                  your account and remove your data from our servers.
                </DialogDescription>
              </DialogHeader>
 <div>
                <div className="grid grid-cols-1 gap-6">
                  <Form {...form}>
                    <form onSubmit={form.handleSubmit(onSubmit)}>
                      {fields.map(({ name, label, description, type }) => (
                        <FormField
                          key={name}
                          control={form.control}
                          name={
                            name as
                              | 'company'
                              | 'role'
                              | 'city'
                              | 'startDate'
                              | 'endDate'
                              | 'description'
                          }
                          render={({ field }) => (
                            <FormItem>
                              <FormLabel>{label}</FormLabel>
                              <FormControl>
                                <Input
                                  type={type}
                                  placeholder={label.toLowerCase()}
                                  {...field}
                                />
                              </FormControl>

                              <FormMessage />
                            </FormItem>
                          )}
                        />
                      ))}

                      <div className="flex justify-end mt-4">
                        {/* Right margin */}

                        <Button type="submit">Save</Button>
                      </div>
                    </form>
                  </Form>
                </div>
              </div>
            </DialogContent>
          </Dialog>
        </div>
      </Card>
    </div>
  );
};

export default WorkExperienceDetail;
const fields = [
  { name: 'company', label: 'Company', description: 'Company Name' },
  { name: 'role', label: 'Role', description: 'Role Here' },
  { name: 'city', label: 'City', description: 'Kaupunki' },
  {
    name: 'startDate',
    label: 'Start Date',
    description: 'Starting Date ',
    type: 'date',
  },
  {
    name: 'endDate',
    label: 'End Date',
    description: 'Ending Date',
    type: 'date',
  },
  {
    name: 'description',
    label: 'Description',
    description: 'Description Here',
    type: 'textarea',
  },
];


How i should approach this kind porlbem with useFormField?
Here is the dialog that opens when user click add new work experience or click a work experience that has been made earlier, so they can modify it.
And in here the work experiences should be listed after added. And if user clicks one of them, then dialog opens where they can modify them.
I'm making the useForm earlier at parent component, because i want to use the .watch function to have live preview from the user data.

export default function ResumeBuilder({ resume }: ResumeMakerProps) {
  const { data: resumeData } = useSWR('/api/resume');
  const form = useForm<formSchemaType>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      personalInfo: {
        name: resume?.personalInfo?.name || defaultFormValues.name,
        email: resume?.personalInfo?.email || defaultFormValues.email,
        phone: resume?.personalInfo?.phone || defaultFormValues.phone,
        address: resume?.personalInfo?.address || defaultFormValues.address,
      },
      workExperience: [],
    },
  });
...

return(
            <WorkExperienceDetail resume={resume} form={form} />
)
this is what the formSchema looks like
import { z } from 'zod';

export const personalSchema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email(),
  phone: z.string().min(2).max(15),
  address: z.string().min(5).max(50),
  //city: z.string().min(2).max(50),
  //occupation: z.string().min(2).max(50),
});

const workExperienceSchema = z.object({
  position: z.string().min(2).max(50),
  company: z.string().min(2).max(50),
  startDate: z.string().min(2).max(50),
  endDate: z.string().min(2).max(50),
  description: z.string().min(2).max(50),
});

export const formSchema = z.object({
  personalInfo: personalSchema,
  workExperience: z.array(workExperienceSchema),
});

export type formSchemaType = z.infer<typeof formSchema>;
Yellow croakerOP
Bump still having same issue🫤
Yellow croakerOP
Tried something like this to get the inputs to the card.
How i can make multiple different form inputs? like role, company etc. Currently there are 0 inputs when i look at the form
  const { fields, append, remove } = useFieldArray({
    control: form.control,
    name: 'workExperience',
  });
...
return(
<div>
                  <Form {...form}>
                    <form onSubmit={form.handleSubmit(onSubmit)}>
                      {fields.map(
                        ({ field, index, name, label, description, type }) => (
                          <FormField
                            key={name}
                            control={form.control}
                            name={
                              name as
                                | 'company'
                                | 'role'
                                | 'city'
                                | 'startDate'
                                | 'endDate'
                                | 'description'
                            }
                            render={({ field }) => (
                              <FormItem>
                                <FormLabel>{label}</FormLabel>
                                <FormControl>
                                  <Input
                                    type={type}
                                    placeholder={label.toLowerCase()}
                                    {...field(`${name}.${index}` as any)}
                                  />
                                </FormControl>

                                <FormMessage />
                              </FormItem>
                            )}
                          />
                        ),
                      )}

                      <div className="flex justify-end mt-4">
                        <Button type="submit">Save</Button>
                      </div>
                    </form>
                  </Form>
</div>
)