TanStackIntermediate
Create Form — Full
RHF + zod + useCreateProduct মিলে CreateForm component — বাংলায়।
6টি অংশবাংলা ভাষায়
src/components/products/product-form.tsxtsx
tsx
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm, Controller } from "react-hook-form";
import { productSchema } from "@/lib/validators/product-schema";
interface ProductFormProps {
// edit-মোড: product থাকলে defaultValues-এ বসবে
defaultValues?: CreateProductInput;
onSubmit: (values: CreateProductInput) => void;
isSubmitting?: boolean;
}
export function ProductForm({
defaultValues,
onSubmit,
isSubmitting,
}: ProductFormProps) {
const form = useForm<CreateProductInput>({
resolver: zodResolver(productSchema),
defaultValues: defaultValues ?? {
name: "",
description: "",
price: 0,
category: "electronics",
stock: 0,
status: "active",
},
});
return (
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-4"
>
<div className="grid gap-3">
<Label>নাম</Label>
<Input
placeholder="যেমন: Wireless Mouse"
{...form.register("name")}
/>
{form.formState.errors.name && (
<FieldError message={form.formState.errors.name.message} />
)}
<Label>বিবরণ</Label>
<Textarea
placeholder="গুণাগুণ লিখুন…"
{...form.register("description")}
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1">
<Label>দাম</Label>
<Input
type="number"
step="0.01"
{...form.register("price")}
/>
{form.formState.errors.price && (
<FieldError message={form.formState.errors.price.message} />
)}
</div>
<div className="space-y-1">
<Label>Stock</Label>
<Input type="number" {...form.register("stock")} />
{form.formState.errors.stock && (
<FieldError message={form.formState.errors.stock.message} />
)}
</div>
<div className="space-y-1">
<Label>Category</Label>
<Controller
control={form.control}
name="category"
render={({ field }) => (
<Select
value={field.value}
onValueChange={field.onChange}
>
<SelectTrigger>
<SelectValue placeholder="Category" />
</SelectTrigger>
<SelectContent>
<SelectItem value="electronics">Electronics</SelectItem>
<SelectItem value="clothing">Clothing</SelectItem>
<SelectItem value="books">Books</SelectItem>
<SelectItem value="food">Food</SelectItem>
</SelectContent>
</Select>
)}
/>
</div>
<div className="space-y-1">
<Label>Status</Label>
<Controller
control={form.control}
name="status"
render={({ field }) => (
<Select
value={field.value}
onValueChange={field.onChange}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="inactive">Inactive</SelectItem>
</SelectContent>
</Select>
)}
/>
</div>
</div>
<Button type="submit" disabled={isSubmitting} className="w-full">
{isSubmitting ? "সেভ হচ্ছে…" : defaultValues ? "আপডেট" : "তৈরি করুন"}
</Button>
</form>
);
}কেন Controller (Select-এ)
shadcn Select শুধু onValueChange/value বোঝে — register-এর input-event pattern মানে না। তাই Controller দিয়ে RHF-কে শুনিয়ে দিচ্ছে: value + onChange. name="category" মাধ্যমেই validation তার schema-র সাথে মিলে।
Dialog-এ ব্যবহার
Create dialogtsx
tsx
const createProduct = useCreateProduct();
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>নতুন Product</DialogTitle>
</DialogHeader>
<ProductForm
isSubmitting={createProduct.isPending}
onSubmit={(values) => {
createProduct.mutate(values, {
onSuccess: () => setOpen(false),
});
}}
/>
</DialogContent>
</Dialog>ডিফল্ট ও রিসেট
defaultValues ?? {...} — create-মোডে ডিফল্ট homepage; edit-মোডে product-এর মান। Dialog বন্ধ হলে ফর্ম-মোড ব্যবহার যোগ্য থাকবে — nested component-এ form.reset() প্রায়ই নতুন dialog খোলায় লাগে।
শেষ প্যারায় নিজে practice করুন — কোড কপি করে নিজের এডিটরে চালিয়ে দেখুন।