React Hook Form - xử lý form dễ dàng hơn bao giờ hết
Mở đầu
Việc xử lý form là một phần không thể tránh khỏi trong công việc của bất kỳ Front-end Developer nào. Tuy nhiên, nếu làm thủ công, quá trình này thường rất tốn thời gian và dễ gây lỗi. Để giải quyết vấn đề này một cách hiệu quả, mình muốn giới thiệu với các bạn React Hook Form — một thư viện quản lý form nhẹ, nhanh và được sử dụng rộng rãi trong cộng đồng React.
React Hook Form giúp giảm thiểu lượng code phức tạp, đồng thời cải thiện hiệu năng và khả năng mở rộng cho ứng dụng của bạn. Trong bài viết này, mình sẽ hướng dẫn các bạn cách sử dụng thư viện này trong dự án ReactJS một cách dễ hiểu và thực tiễn.
Tải xuống
NPM
npm install react-hook-form
YARN
yarn add react-hook-form
Ví dụ cơ bản
Ví dụ cơ bản về cách sử dụng React Hook Form:
import React from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit, formState: { errors } } = useForm({
criteriaMode: "all"
});
return (
// Hàm handleSubmit sẽ validate trước khi gọi hàm onSubmit
<form onSubmit={handleSubmit(onSubmit)}>
// đăng kí input cho Hook vói tên example
<input defaultValue="test" {...register("example")} />
// đăng kí thẻ input với React-Hook-Form với tên "exampleRequired"
// validate là required
<input {...register("exampleRequired", { required: true })} />
// xử lý lỗi bằng đối tượng errors được trả về từ useForm
{errors.exampleRequired && <span>This field is required</span>}
<input type="submit" />
</form>
);
}
Khái niệm chính
useForm
useForm là một custom hook của React Hook Form, giúp bạn quản lý trạng thái và xử lý form một cách dễ dàng và hiệu quả. Mỗi lần gọi useForm sẽ tạo ra một instance riêng biệt, gắn liền với vòng đời của một form cụ thể trong ứng dụng của bạn.
Tham số truyền vào
Ví dụ:
const { register, handleSubmit, formState: { errors } } = useForm({
mode: 'onSubmit',
reValidateMode: 'onChange',
defaultValues: {},
resolver: undefined,
criteriaMode: "firstError",
})
-
mode
: có các giá trị onChange | onBlur | onSubmit | onTouched | all . Dùng để cấu hình một chiến lược validate trước khi submit form.- onSubmit: sẽ thực hiện validate khi submit form, và những element không hợp lệ sẽ được lắng nghe sự thay đổi và sau đó tiếp tục validate những element đó bằng mode onChange.
- onChange: sẽ thực hiện validate khi mỗi khi onChange element, và nó dẫn đến re-render nhiều lần ( cần cân nhắc khi sử dụng ).
- onBlur: sẽ thực hiện validate mỗi khi element có sự kiện blur.
- onTouch: sẽ thực hiện validate cho lần blur đầu tiên, sau đó sẽ thực hiện validate cho mỗi lần change event.
- all: sẽ thực hiện validate khi blur và change event.
-
defaultValues
: thiết lập giá trị mặc định ( lần đầu ) cho form. -
criteriaMode
: có các giá trị firstError | all. Đối với firstError sẽ chỉ nhận được một lỗi đầu tiên, all sẽ nhận được tất cả lỗi. -
resolver
: dùng để hổ trợ validate đối với thirt-party ( yup, zod ).
Giá trị trả về
Register
register là một trong những thành phần cốt lõi của React Hook Form, dùng để đăng ký các input hoặc component của bạn với hook. Khi đăng ký, React Hook Form sẽ theo dõi và quản lý giá trị cũng như validation của các trường này.
function App() {
const { register, handleSubmit } = useForm();
const onSubmit = data => {
console.log(data); // { name: ... }
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} />
<button type="submit">Submit!!!<button />
</form>
);
}
handleSubmit
Khi form được submit thì handleSubmit sẽ được gọi và thực hiện validate, nếu validate thành công sẽ tiếp tục gọi hàm onSubmit.
ví dụ:
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} />
<button type="submit">Submit!!!<button />
</form>
formState
import React from "react";
import { useForm } from "react-hook-form";
export default function App() {
const {
register,
handleSubmit,
formState: { errors, isDirty, isSubmitting, isSubmitted, submitCount, isValid, isValidating },
} = useForm();
const onSubmit = (data: FormInputs) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("test")} />
<input type="submit" />
</form>
);
}
Các giá trị của formState:
- errors trả về một object chứa lỗi, và key của object tương ứng với name mà chúng ta truyền vào hàm Register. Nếu trường hợp không có lỗi nào thì sẽ trả về object rỗng.
- isDirty so sánh data input hiện tại với defaultValue và trả về true nếu value hiện tại khác với defaultValue ( dùng để show pop-up khi user chỉnh sửa nhưng chưa submit ).
- isSubmitting trả về true khi form đang submit.
- isSubmitted trả về true khi form submit xong.
- submitCount trả về thời gian ( dạng number ) submit form .
- isValid trả về true nếu không có bất kì lỗi nào, false khi có lỗi.
- isValidating trả về true nếu đang validate.
watch
watch sẽ lắng nghe sự thay đổi về giá trị ( giống như sự kiện onChange ).
import React from "react";
import { useForm } from "react-hook-form";
function App() {
const { register, watch, formState: { errors }, handleSubmit } = useForm();
const watchShowAge = watch("showAge", false); // đăng kí watch cho field showAge, truyền giá trị mặc định bằng đối số thứ 2
const watchAllFields = watch(); // nếu không truyền tham số, watch sẽ lắng nghe và trả về tất cả fields
const onSubmit = data => console.log(data);
return (
<>
<form onSubmit={handleSubmit(onSubmit)}>
<input type="checkbox" {...register("showAge")} />
<input type="submit" />
</form>
</>
);
}
reset
reset được sử dụng để thiết lập lại giá trị của toàn bộ form về trạng thái mặc định. Thường dùng khi bạn muốn làm mới form, ví dụ sau khi gọi API thành công, đặt lại giá trị form để đồng bộ với dữ liệu mới hoặc để reset trạng thái isDirty.
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit, reset } = useForm();
const onSubmit = (data) => {
// call API
const data = fetchListUser();
reset(data); // đặt lại giá trị mặc định cho Form
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName", { required: true })} />
<input {...register("lastName")} />
<input type="submit" />
</form>
);
}
setError
Trong một số trường hợp bạn muốn tự tạo lỗi (ví dụ: lỗi từ server hoặc lỗi logic tuỳ chỉnh), bạn có thể sử dụng setError để thiết lập lỗi cho một trường cụ thể.
import { useForm } from "react-hook-form";
const App = () => {
const { register, handleSubmit, setError, formState: { errors } } = useForm();
const onSubmit = data => {
console.log(data)
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('firstName'}/>
{errors.firstName && <p>{errors.firstName.message}</p>}
<button onClick={() => setError("firstName",'Message Error')}} >
Set Error
</button>
</form>
);
};
clearError
Đã có setError thì phải có clearError, nó dùng để xóa một lỗi nào đó khỏi errors dựa vào name.
import { useForm } from "react-hook-form";
const App = () => {
const { register, handleSubmit, clearError, formState: { errors } } = useForm();
const onSubmit = data => {
console.log(data)
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('firstName'}/>
{errors.firstName && <p>{errors.firstName.message}</p>}
<button onClick={() => clearError("firstName")}} >
Clear Error
</button>
</form>
);
};
setValue
setValue() cho phép bạn gán hoặc cập nhật giá trị cho bất kỳ trường nào đã được đăng ký.
Ví dụ: Tôi muốn xóa giá trị của ô input khi nhấn vào button.
import { useForm } from "react-hook-form";
const App = () => {
const { register, handleSubmit, setValue, formState: { errors } } = useForm();
const onSubmit = data => {
console.log(data)
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('firstName'}/>
{errors.firstName && <p>{errors.firstName.message}</p>}
<button onClick={() => setValue("test",'')}} > // set value của ô input thành '' ( rỗng )
Clear Value
</button>
</form>
);
};
setFocus
setFocus() là một hàm tiện ích được cung cấp bởi React Hook Form, giúp bạn đặt con trỏ (focus) vào một input cụ thể — thường dùng khi muốn cải thiện trải nghiệm người dùng, như tự động focus vào input đầu tiên khi màn hình load.
Ví dụ: Tôi muốn focus vào ô input firstName sau khi màn hình được render.
export default function App() {
const { register, handleSubmit, setFocus } = useForm();
const onSubmit = (data) => console.log(data);
useEffect(() => {
setFocus("firstName");
}, []);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName")} placeholder="First Name" />
<input type="submit" />
</form>
);
}
getValues
getValues() là hàm giúp bạn lấy dữ liệu hiện tại của các input đã được đăng ký trong form. Hàm này rất hữu ích khi bạn cần truy cập dữ liệu form mà không cần submit hoặc xử lý logic điều kiện dựa trên giá trị hiện tại.
import React from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, getValues } = useForm();
return (
<form>
<input {...register("test")} />
<input {...register("test1")} />
<button
type="button"
onClick={() => {
const values = getValues(); // { test: "test-input", test1: "test1-input" }
const singleValue = getValues("test"); // "test-input"
const multipleValues = getValues(["test", "test1"]); // ["test-input", "test1-input"]
}}
>
Get Values
</button>
</form>
);
}
trigger
Trong một số tình huống, bạn cần kiểm tra tính hợp lệ của một hoặc nhiều trường một cách thủ công (ví dụ: sau khi người dùng thay đổi dữ liệu, hoặc khi nhấn vào một nút tùy chỉnh), bạn có thể sử dụng hàm trigger() do React Hook Form cung cấp.
Ví dụ:
trigger("lastName"); // chỉ trigger lastName
trigger(["firstName", "lastName"]); // trigger lastName firstName
trigger(); // trigger tất cả
Ví dụ thực tế - Tôi cần kiểm tra email có hợp lệ không sau khi người dùng blur ra khỏi ô input nhập email:
<input
{...register("email")}
onBlur={() => trigger("email")}
/>
Validation và xử lý lỗi như thế nào ?
Validation là một phần cực kỳ quan trọng trong việc xử lý form, và React Hook Form cung cấp sẵn các cơ chế giúp bạn thực hiện việc này dễ dàng và hiệu quả.
Thư viện hỗ trợ sẵn một số rule cơ bản như:
- required: bắt buộc nhập
- min, max: giới hạn giá trị số
- minLength, maxLength: giới hạn độ dài chuỗi
- pattern: kiểm tra theo regex
import { ErrorMessage } from "@hookform/error-message";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit, formState: { errors } } = useForm({
criteriaMode: "all"
});
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h1>ErrorMessage</h1>
<input
{...register("multipleErrorInput", {
// xử lý lỗi khi bỏ trống
required: "This input is required.",
// xử lý lỗi khi nhập khác giá trị là số
pattern: {
value: /\d+/,
message: "This input is number only."
},
// xử lý lỗi khi nhập nhiều hơn 10 kí tự
minLength: {
value: 11,
message: "This input must exceed 10 characters"
}
})}
/>
// ErrorMessage được import từ thư viện
<ErrorMessage
// nhận vào props errors ( errors lấy từ formState ở trên ) nó sẽ trigger khi phát hiện lỗi khác.
errors={errors}
// name tương ứng với name input đã đăng kí với hook
name="multipleErrorInput"
// sẽ render ra giao diện khi có error, ở đây là thẻ p
render={({ messages }) => {
return messages.map((message,index) => (
<p key={index}>{message}</p>
))
: null;
}}
/>
<input type="submit" />
</form>
);
}
Validate với yup
React Hook Form hỗ trợ tích hợp với thư viện Yup để thực hiện việc kiểm tra và xác thực dữ liệu đầu vào một cách thuận tiện và mạnh mẽ. Để sử dụng Yup trong React Hook Form, bạn cần:
- Cài đặt @hookform/resolvers.
- Cài đặt yup.
- Tạo một schema bằng Yup để mô tả các quy tắc kiểm tra dữ liệu.
- Truyền schema đó vào useForm thông qua resolver.
Dưới đây là một ví dụ minh họa cách sử dụng:
import { useForm } from "react-hook-form";
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from "yup";
// tạo schema để validate
const schema = yup.object({
firstName: yup.string().required(),
age: yup.number().positive().integer().required(),
}).required();
export default function App() {
const { register, handleSubmit, formState:{ errors } } = useForm({
// resolver dùng để validate với yup
resolver: yupResolver(schema)
});
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName")} />
<p>{errors.firstName?.message}</p>
<input {...register("age")} />
<p>{errors.age?.message}</p>
<input type="submit" />
</form>
);
}
Controller
Khi làm việc với các UI component bên ngoài như Material UI, Ant Design, React Select, React DatePicker... thì không thể sử dụng trực tiếp register để liên kết form với các input này được. Lý do là vì các component này không hoạt động như các thẻ <input/> thông thường.
- Trong những trường hợp như vậy, React Hook Form cung cấp một thành phần đặc biệt gọi là Controller để giúp bạn tích hợp dễ dàng.
- Controller đóng vai trò là cầu nối giữa React Hook Form và các component bên ngoài.
- Thuộc tính control (lấy từ useForm) sẽ cung cấp các phương thức cần thiết để quản lý input.
- Bên trong render, bạn có thể sử dụng các props như onChange, onBlur, value, ref... để kết nối với component.
Ví dụ:
function App() {
const { handleSubmit, control } = useForm();
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<Controller
control={control}
name="ReactDatepicker"
render={({ field: { onChange, onBlur, value, ref } }) => (
<ReactDatePicker
onChange={onChange}
onBlur={onBlur}
selected={value}
/>
)}
/>
<input type="submit" />
</form>
);
}
Kết thúc
Trên đây là những khái niệm và ví dụ cơ bản nhất để bạn bắt đầu làm quen với React Hook Form — một thư viện mạnh mẽ giúp việc quản lý form trong React trở nên dễ dàng, hiệu quả và ít boilerplate hơn.
Hy vọng bài viết này sẽ giúp bạn tiết kiệm thời gian trong quá trình phát triển dự án. Cảm ơn bạn đã đọc đến đây! 🚀 Nếu thấy hữu ích, đừng ngần ngại chia sẻ với mọi người nhé! 😄
Tài liệu tham khảo
All Rights Reserved