Shadcn Form Select + Server Action
Asked Answered
S

3

8

I am not able to read the select value in shadcn form Select. I am using serveraction. So, I am creating a form to send emails, i am able to read the values of other fields but not the value for Select tag

THIS IS THE FORM

'use client'

import * as z from "zod"
import React, { useState } from 'react'
import { zodResolver } from "@hookform/resolvers/zod"
import { Message, useForm } from "react-hook-form"
 
import { Button } from "@/components/ui/button"
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import {toast} from "react-hot-toast"

import { sendEmail} from "@/actions/integrations/Email.actions"
import EmailSubmitButton from "./EmailSubmitButton"
import { useFormState } from "react-dom"
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"


const formSchema = z.object({
  from: z.string()
        .email()
  ,
  to: z.string()
        .email()
  ,
  subject: z.string().min(3, {
    message: "Subject is required and must be of atleast 3 characters.",
  }),
  message: z.string().min(10, {
    message: "Message is required and must be of atleast 10 characters.",
  }),
  calendlylink : z.string()
})


const EmailApplicantForm = () => {

  const initialState = {message  : null, errors : { } };
  const [state, dispatch] = useFormState(sendEmail, initialState)
  
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      from: "[email protected]", 
      to: "[email protected]",
      subject: "",
      message: "",
      calendlylink : ""
    },
  })

  console.log('STATE', state)

  type InterviewLinkType = {
    id : string,
    title : string,
    link : string
  }
  
  const interviewLinks: InterviewLinkType[] = [
    {
      id : '1',
      title : 'Interview For Frontend Engineer Round-1',
      link : 'https://calendly.com/sobitp59/interview-round-1',
    },
    {
      id : '2',
      title : 'Interview for Backend Engineer Round-2',
      link : 'https://calendly.com/sobitp59/interview-for-backend-engineer-round-2',
    },
    {
      id : '3',
      title : 'Interview for Backend Engineer Round-3',
      link : 'https://calendly.com/sobitp59/interview-for-backend-engineer-round-3',
    },
    {
      id : '4',
      title : 'Interview for Backend Engineer Round-4',
      link : 'https://calendly.com/sobitp59/interview-for-backend-engineer-round-4',
    },
  ]


  return (
    <Form {...form}>
    <form action={dispatch}
      className="space-y-8">
     
      <!-- OTHER FIELDS  -->
      
      <FormField
        control={form.control}
        name="calendlylink"
        render={({ field }) => (
          <FormItem>
            <FormLabel>Select calendly link</FormLabel>
              <Select onValueChange={field.onChange} value={field.value}>
            <FormControl>

              <SelectTrigger>
                <SelectValue placeholder="Choose your calendly link"/>
              </SelectTrigger>
            </FormControl>
              <SelectContent>
              {interviewLinks.map(({id, title, link} : InterviewLinkType) => (
                  <SelectItem key={id.toString()} value={link}>{title}</SelectItem>
                  ))}
              </SelectContent>
              </Select>
          </FormItem>
        )}
      />

      <div className="flex justify-end">
        <EmailSubmitButton />
      </div>
      
    </form>
  </Form>
  )
}

export default EmailApplicantForm

THIS IS THE ACTION

"use server"

import { z } from 'zod'
import React from "react";
import { Resend } from "resend";

import SendCandidateEmailTemplate from "@/components/applicants/email_applicant_form/EmailTemplate";
const resend = new Resend(process.env.RESEND_API_KEY);

import {
    UNAUTHORIZED_ACTION,
  } from "@/lib/exceptions/exceptions";

import { currentUser } from '@clerk/nextjs';
 
const sendEmailFormSchema = z.object({
    to: z.string()
        .email()
    ,
    from: z.string()
        .email()
    ,
    message: z.string()
        .min(10, {message : 'Message is required and should be atleast of 10 characters.'})
    ,
    subject: z.string()
        .min(3, {message : 'Subject is required and should be atleast of 3 characters.'})
        ,
    calendlylink : z.string()
        .min(3, {message : 'Subject is required and should be atleast of 3 characters.'})
})

export type State = {
    errors?: {
        from ?: string[];
        to ?: string[];
        subject?: string[];
        message?: string[];
        calendlylink ?: string[]
      };
    message?: string | null;
}

export async function sendEmail(prevState : State, formData : FormData){

    console.log("FORM DATA", formData)

    const validateFields =  sendEmailFormSchema.safeParse({
        from : formData.get('from'),
        to : formData.get('to'),
        subject : formData.get('subject'),
        message : formData.get('message'),
        calendlylink : formData.get('calendlylink')
    })

    if(!validateFields.success) {
        return {
          errors: validateFields.error.flatten().fieldErrors,
          message: 'Missing Fields. Failed to Send Email.',
        };
    }

    const {to, from, subject, message, calendlylink } = validateFields.data;
    console.log('FOMR DATA', {to, from, subject, message, calendlylink})
    try {
        const user = await currentUser();

        if (!user) {
            return UNAUTHORIZED_ACTION();
        }

        
        const data = await resend.emails.send({
            from: 'TEST <[email protected]>',
            to: '[email protected]',
            subject: subject,
            react: React.createElement(SendCandidateEmailTemplate, {
                message : message,
                candidateName   : 'Sobit',
                subject : subject
            }),
        });
        
        return {data : data};
    } catch (error) {
        return {
            message: 'Failed to send email.',
          }; 
    }
}

I am trying server side validation. I should get the formData with the selected value. but not able to read that data. I have tried reading other blogs but there is not proper answer for server action. I am not able to figure it out

Silberman answered 4/12, 2023 at 7:20 Comment(2)
Hi, did you solve this? I am facing the same issue today. It works if I use the regular <select /> html element. But it doesn't work with the shadcn <Select /> component.Mugwump
I have the exact problem, I mean it's a common use-case, but not many documentation on that. Good question tho.Pincushion
S
1

You cannot retrieve the value of shadcn select by form submit action because it is not a real form element. So the form is not aware of its existence. The solution is using a hidden html element. Before submit the form, put the values of shadcn select into hidden html element. You can retrieve the values via html hidden element.

Setback answered 11/1 at 14:50 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Collencollenchyma
E
0

I added the select value on submit function:

<form
  ref={formRef}
  onSubmit={(evt) => {
  evt.preventDefault()
  form.handleSubmit((values) => {
      const formData = new FormData(formRef.current!)
      formData.append('selectValue', values.product)
      formAction(formData)
    })(evt)
  }}
>
...
</form>
Embower answered 8/8 at 11:8 Comment(0)
C
0

You need to put the name property in the Select component a exemple:

          <Select name={name}>
            <SelectTrigger className="w-80 m-auto mt-2">
              <SelectValue placeholder={placeholder} />
            </SelectTrigger>
            <SelectContent>
              {options?.map((option) => (
                <SelectItem value={option} key={option}>
                  {option}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>
Chalmer answered 10/9 at 19:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.