Tạo Form Mượt Mà với shadcn/ui và TanStack Form
Trong bài viết này, tôi sẽ chia sẻ hành trình của mình khi chuyển đổi sang TanStack và cách mà tôi đã kết hợp thành công shadcn/ui với TanStack Form để tạo ra các form đẹp mắt và an toàn về kiểu dữ liệu. Nếu bạn là một lập trình viên đang tìm kiếm giải pháp tối ưu cho việc quản lý trạng thái form, bài viết này sẽ mang lại nhiều thông tin hữu ích cho bạn.
Giới Thiệu
Khi tôi bắt đầu chuyển đổi từ Next.js sang TanStack, tôi đã gặp phải một số vấn đề với việc quản lý form. Tôi rất thích giao diện và trải nghiệm lập trình viên mà shadcn/ui mang lại, nhưng thành phần form mặc định của nó lại dựa trên react-hook-form. Với quyết định hoàn toàn chuyển sang hệ sinh thái TanStack, tôi đã tìm kiếm một cách để tích hợp cả hai mà không cần phải hy sinh bất kỳ yếu tố nào.
Tại Sao Nên Kết Hợp?
TanStack Form nổi bật với khả năng quản lý trạng thái form an toàn về kiểu dữ liệu và không phụ thuộc vào framework. Trong khi đó, shadcn/ui cung cấp các thành phần đẹp mắt và dễ sử dụng. Mục tiêu của tôi là kết hợp những lợi ích tốt nhất từ cả hai thư viện mà không phải đánh đổi. Component mới này sẽ mang đến cho bạn:
- An toàn về kiểu dữ liệu hoàn toàn: Suy diễn kiểu dữ liệu trực tiếp từ các schema xác thực (như Zod, Valibot, vv).
- Tích hợp mượt mà với TanStack: Tận dụng các logic quản lý trạng thái và xác thực của TanStack Form.
- Phong cách đồng nhất của shadcn/ui: Sử dụng các thành phần form mà bạn đã quen thuộc.
Hướng Dẫn Bắt Đầu
Việc thiết lập rất đơn giản. Hãy làm theo các bước dưới đây và bạn sẽ có những form đẹp mắt và an toàn về kiểu dữ liệu chỉ trong vài phút.
1. Cài Đặt shadcn/ui
Nếu bạn chưa cài đặt shadcn/ui trong dự án của mình, hãy thực hiện lệnh sau:
npx shadcn-ui@latest init
2. Cài Đặt Thư Viện TanStack Form
Tiếp theo, thêm thư viện TanStack Form vào dự án:
npm install @tanstack/react-form
3. Sao Chép Các Thành Phần Cơ Bản
Giải pháp của tôi bao gồm hai tệp chính. Tải chúng từ kho GitHub và đặt chúng vào các thư mục thích hợp:
form.tsx: Sao chép vào thư mụccomponents/ui.form-hook.tsx: Sao chép vào thư mụchookstrong thư mục gốc của dự án.
4. Thêm Các Thành Phần shadcn/ui
Cuối cùng, thêm các thành phần shadcn/ui mà bạn cần cho các form của mình:
npx shadcn-ui@latest add button input label form
Cách Sử Dụng
Khi đã cài đặt xong, việc sử dụng thành phần này rất dễ dàng. Cấu trúc sẽ rất quen thuộc đối với bất kỳ ai đã sử dụng thành phần form gốc của shadcn/ui.
1. Định Nghĩa Logic Form
Đầu tiên, định nghĩa schema cho form của bạn (sử dụng Zod trong ví dụ này) và tạo instance form với hook useAppForm. Tại đây, bạn sẽ thiết lập giá trị mặc định, các xác thực và handler cho onSubmit.
javascript
import { useAppForm } from "@/hooks/form-hook";
import { z } from "zod";
const userSchema = z.object({
email: z.string().email("Địa chỉ email không hợp lệ"),
});
export function MyFormComponent() {
const form = useAppForm({
defaultValues: {
email: "",
},
validators: {
onChange: userSchema,
},
onSubmit: async ({ value }) => {
alert(`Chào ${value.email}!`);
},
});
// ...
}
2. Xây Dựng Thành Phần Form
Bây giờ, hãy xây dựng form của bạn bằng cách sử dụng các thành phần đã được cung cấp. Thành phần <form.AppForm> liên kết instance form của bạn, và <form.AppField> kết nối từng input với trạng thái form.
Dưới đây là một ví dụ hoàn chỉnh:
javascript
import { useAppForm } from "@/hooks/form-hook";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
} from "@/components/ui/form";
const userSchema = z.object({
email: z.string().email("Địa chỉ email không hợp lệ"),
});
export function EmailForm() {
const form = useAppForm({
defaultValues: {
email: "",
},
validators: {
onChange: userSchema,
},
onSubmit: async ({ value }) => {
// Giả lập gửi trong 500ms
await new Promise((resolve) => setTimeout(resolve, 500));
alert(`Chào ${value.email}!`);
},
});
return (
<form.AppForm>
<Form className="space-y-4">
<form.AppField name="email">
{(field) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
type="email"
placeholder="Nhập địa chỉ email của bạn"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
/>
</FormControl>
<FormDescription>
Chúng tôi sẽ không chia sẻ email của bạn với bất kỳ ai khác.
</FormDescription>
<FormMessage />
</FormItem>
)}
</form.AppField>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
>
{([canSubmit, isSubmitting]) => (
<Button type="submit" disabled={!canSubmit}>
{isSubmitting ? "Đang gửi..." : "Gửi"}
</Button>
)}
</form.Subscribe>
</Form>
</form.AppForm>
);
}
Như bạn thấy, <form.Subscribe> cho phép bạn lắng nghe trạng thái của form, điều này rất hữu ích để vô hiệu hóa nút gửi trong khi form không hợp lệ hoặc đang trong quá trình gửi.
Thực Hành Tốt Nhất
- Sử dụng xác thực: Luôn luôn xác thực dữ liệu đầu vào để đảm bảo tính hợp lệ và an toàn cho người dùng.
- Tối ưu hóa giao diện: Đảm bảo rằng các thành phần UI dễ sử dụng và thân thiện với người dùng.
- Kiểm tra kỹ lưỡng: Trước khi triển khai, hãy kiểm tra các form trong nhiều tình huống khác nhau để đảm bảo không có lỗi xảy ra.
Những Cạm Bẫy Thường Gặp
- Bỏ qua xác thực: Việc không thực hiện xác thực có thể dẫn đến lỗi và trải nghiệm người dùng kém.
- Không quản lý trạng thái: Đảm bảo rằng bạn đang theo dõi trạng thái của form để đưa ra phản hồi kịp thời cho người dùng.
Mẹo Tối Ưu Hiệu Suất
- Sử dụng memoization: Sử dụng hook
useMemovàuseCallbackđể tối ưu hóa hiệu suất khi render. - Tránh render lại không cần thiết: Chỉ render lại những thành phần thực sự cần thiết để tiết kiệm tài nguyên.
Giải Quyết Vấn Đề
Nếu bạn gặp phải lỗi trong quá trình sử dụng, hãy kiểm tra các thông báo lỗi trong console và đảm bảo rằng tất cả các thành phần đều đã được nhập đúng cách.
Kết Luận
Component này được phát triển từ nhu cầu thực tế trong dự án của tôi và tôi tin rằng nó sẽ giúp ích cho những ai đang tìm kiếm giải pháp form an toàn và dễ sử dụng. Hãy thử nghiệm nó trong dự án tiếp theo của bạn! Tôi rất mong nhận được phản hồi từ bạn. Nếu bạn có giải pháp yêu thích nào cho các form trong React, hãy cho tôi biết trong phần bình luận dưới đây!
Bài viết được đăng lần đầu trên blog của tôi