next.jsreact-hook-formmaven-shade-pluginshadcnuiradix-ui

Issue with react-hook-form Controller and ShadeCn Select Component: Value Resets to Empty String on Selection


I'm working on a React project using react-hook-form along with the Controller component to manage a form with Select components. However, I'm running into some issues:

Initial Form Submission:

When I submit the form without interacting with the Select components, everything works as expected, and the default values are submitted correctly.

Issue After Selection:

If I interact with any Select component (e.g., propertyType) and make a selection, the value for that field in the submitted form data becomes an empty string. Placeholder Issue:

The SelectTrigger does not show the default value or selected option. It only shows the placeholder text ("Select a property type") after making a selection, and even then, it does not display the correct option.

My Code :

"use client";

import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form";

const formSchema = z.object({
  location: z.string().min(1, "Location is required"),
  propertyType: z.string(),
  purpose: z.string(),
  priceRange: z.string(),
  beds: z.string(),
  filters: z.string(),
});

const propertyTypeOptions = [
  { value: "residential", label: "All in Residential" },
  // Add more options as needed
];

export default function PropertyFilter() {
  const form = useForm({
    resolver: zodResolver(formSchema),
    defaultValues: {
      location: "",
      propertyType: propertyTypeOptions[0].value,
      purpose: "rent",
      priceRange: "any",
      beds: "any",
      filters: "baths-area",
    },
  });

  function onSubmit(values) {
    console.log(values);
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="form-container">
        
        <FormField
          control={form.control}
          name="propertyType"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Property Type</FormLabel>
              <FormControl>
                <Controller
                  name="propertyType"
                  control={form.control}
                  render={({ field }) => (
                    <Select
                      onValueChange={field.onChange}
                      value={field.value}
                    >
                      <SelectTrigger>
                        <SelectValue placeholder="Select a property type" />
                      </SelectTrigger>
                      <SelectContent>
                        {propertyTypeOptions.map((option) => (
                          <SelectItem key={option.value} value={option.value}>
                            {option.label}
                          </SelectItem>
                        ))}
                      </SelectContent>
                    </Select>
                  )}
                />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        
        <button type="submit">Submit</button>
      </form>
    </Form>
  );
}

The SelectTrigger should display the selected option or default value correctly. The submitted form data should include the correct value from the Select component after making a selection.

The SelectTrigger initially does not show any text. After selecting an option, the form data contains an empty string for that field.

My updated approach :

"use client";
import React from "react";
import { Controller, Control, FieldValues, Path } from "react-hook-form";
import { Input } from "@/components/ui/input";
import {
  FormItem,
  FormLabel,
  FormControl,
  FormMessage,
} from "@/components/ui/form";

interface InputFieldFormProps<T extends FieldValues> {
  name: Path<T>;
  label: string;
  placeholder: string;
  control: Control<T>;
  required?: boolean;
  type?: string;
  className?: string;
  is_supported_rtl: boolean;
}

const InputFieldForm = <T extends FieldValues>({
  name,
  label,
  placeholder,
  control,
  required = false,
  type = "text",
  className = "",
  is_supported_rtl,
}: InputFieldFormProps<T>) => {
  return (
    <FormItem className={`w-full space-y-2 ${className}`}>
      <FormLabel>{label}</FormLabel>
      <FormControl>
        <Controller
          name={name}
          control={control}
          render={({ field, fieldState }) => (
            <>
              <Input
                placeholder={placeholder}
                type={type}
                required={required}
                className="h-[42px] rounded-[10px] border border-[#E9E9E9] bg-white px-[15px] py-[10px]"
                {...field}
                value={field.value ?? ""}
                dir={is_supported_rtl ? "rtl" : "ltr"}
                onChange={(event) => {
                  const value = event.target.value;
                  if (type === "number") {
                    const numValue = parseFloat(value);
                    field.onChange(isNaN(numValue) ? "" : numValue);
                  } else {
                    field.onChange(value);
                  }
                }}
              />
              <FormMessage className="text-primary">
                {fieldState.error?.message}
              </FormMessage>
            </>
          )}
        />
      </FormControl>
    </FormItem>
  );
};

export default InputFieldForm;


Solution

  • I just encountered the exact same issue as you described with the exact same use case. It looks like I customised the SelectItem component within components/ui/select.tsx. I removed the SelectPrimitive.ItemText that is by default, wrapping the children.

    I think you should check if you didn't do the same mistake