I want to create two Select components using ShadCN. I want these selects to have a dependency between each other.
This means that after a value is selected for categories, then the select of subcategories is populated with the filtered data accordingly.
This step is working fine.
However, when I click on submit the form, it is as if the category was an empty value and I believe it is because I am using a custom handle function in the onValueChange
event instead of using the field.onChange
.
I still haven't gotten the hang of using the Form
components and all its intricacies so I don't understand how I can use my own change function to handle the events or even how I could have these components as reusable components and avoid DRY.
#... imports
export const TransactionForm = () => {
const [selectedCategory, setSelectedCategory] = useState<string>("");
const [selectedSubcategory, setSelectedSubcategory] = useState<string>("");
const [filteredSubcategories, setFilteredSubcategories] = useState<string[]>(
[]
);
const form = useForm<z.infer<typeof TransactionSchema>>({
resolver: zodResolver(TransactionSchema),
defaultValues: {
category: "",
subcategory: "",
},
});
const categories = [...dummyData.categories].map((item) => item.name);
const onSubmit = (values: z.infer<typeof TransactionSchema>) => {
console.log(values);
};
const handleCategoryChange = (category: string) => {
setSelectedCategory(category);
setSelectedSubcategory("");
console.log(category);
const categoryData = dummyData.categories.find(
(item) => item.name === category
);
if (categoryData) {
setFilteredSubcategories(categoryData.subcategories);
} else {
setFilteredSubcategories([]);
}
};
const handleSubcategoryChange = (subcategory: string) => {
setSelectedSubcategory(subcategory);
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<div className="space-y-4">
<FormField
control={form.control}
name="category"
render={({ field }) => (
<FormItem>
<FormLabel>Category:</FormLabel>
<Select
onValueChange={(value) => handleCategoryChange(value)}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger className="w-[180px] h-[40px]">
<SelectValue placeholder="Category" />
</SelectTrigger>
</FormControl>
<SelectContent className="bg-white">
<SelectGroup>
<SelectLabel>Category</SelectLabel>
{categories.map((option, index) => (
<SelectItem key={index} value={option}>
{option}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</FormItem>
)}
/>
<FormField
control={form.control}
name="subcategory"
render={({ field }) => (
<FormItem>
<FormLabel>Subcategory:</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
{...field}
>
<FormControl>
<SelectTrigger className="w-[180px] h-[40px]">
<SelectValue placeholder="Subcategory" />
</SelectTrigger>
</FormControl>
<SelectContent className="bg-white">
<SelectGroup>
<SelectLabel>Subcategory</SelectLabel>
{filteredSubcategories.map((option, index) => (
<SelectItem key={index} value={option}>
{option}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</FormItem>
)}
/>
</div>
<Button type="submit" className="w-full">
Save
</Button>
</form>
</Form>
</CardWrapper>
);
};
You are facing this issue because you are not setting selected category value to form, here is how you can do that in your case:
const handleCategoryChange = (category: string) => {
setSelectedCategory(category);
setSelectedSubcategory("");
console.log(category);
form.setValue("category",category) // setting category value to form
const categoryData = dummyData.categories.find(
(item) => item.name === category
);
if (categoryData) {
setFilteredSubcategories(categoryData.subcategories);
} else {
setFilteredSubcategories([]);
}
};
Note: You can avoid extra useState
hooks, if you want a reactive value of any form item, you can use form.watch()