DEV Community

Cover image for Form trong React - formik, final-form, react-hook-form
David Tran
David Tran

Posted on • Edited on

Form trong React - formik, final-form, react-hook-form

Form is everywhere, form là một phần tất yếu của mọi website. Và khi xây dựng một webapp bằng React, việc xử lý form là một điều bắt buộc sẽ xảy ra.

Bạn có thể lựa chọn xử lý theo cách của riêng mình hoặc là lựa chọn một thư viện có sẵn của cộng đồng. Tuy nhiên khi lựa chọn một thư viện thứ 3 thì bạn sẽ gặp ngay vấn đề là có quá nhiều thư viện để bạn lựa chọn.

Việc lựa chọn 1 thư viện cũng rất là hóc búa, việc lựa chọn bừa một thư viện có nhiều star trên Github không phải lúc nào cũng sáng suốt. Bạn chắc chắn sẽ cần đọc document, code example và thử nghiệm nhiều lần mới có thể chắc chắn đây là thư viện bạn muốn sử dụng cho dự án của mình.
Đối với việc xử lý form, thì chúng ta sẽ có những ứng cử viên như sau:

Tên thư viện Star Github Lượt download Kích thước
formik 23.4K 868k/tuần 7.22 kB
final-form 2.5K 222k/tuần 5.1 kB
react-Form 1.9K 12k/tuần 4.3 kB
react-hook-form 12.5K 270k/tuần 8.67 kB
redux-form 12.3K 389k/tuần 26.4 kB

Trong phạm vi bài viết này, mình sẽ so sánh 3 thư viện có lượt download nhiều nhất là formik, final-form và react-hook-form

Đối với redux-form thì chúng ta có thể an toàn bỏ qua, năm 2020 rồi chẳng ai lại đi lưu từng keystore của input vào trong Redux nữa, cực kỳ ảnh hướng tới performance. Đồng thời tác giả của thư viện này khuyên mọi người nên chuyển qua sử dụng final-form rồi 🙂

So sánh về cách sử dụng

Formik

Xét về các chỉ số như số lượng stars, lượt download thì formik là thư viện đang được nhiều người sử dụng nhất, nhiều lượt star trên Github nhất và số lần download nhiều nhất.

Thành thực mà nói thì tôi không thích câu sologan của formik (build form without tears). Bởi vì việc xây dựng form cũng như validation trong React chưa bao giờ là vấn đề quá phức tạp.

Để sử dụng formik, bạn sẽ cần render Form component của formik ngay trong phần render:

import React from "react";
import { Formik } from "formik";

const Basic = () => (
  <div>
    <h1>Anywhere in your app!</h1>
    <Formik
      initialValues={{ email: "", password: "" }}
      validate={(values) => {
        const errors = {};
        if (!values.email) {
          errors.email = "Required";
        } else if (
          !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
        ) {
          errors.email = "Invalid email address";
        }
        return errors;
      }}
      onSubmit={(values, { setSubmitting }) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2));
          setSubmitting(false);
        }, 400);
      }}
    >
      {({
        values,
        errors,
        touched,
        handleChange,
        handleBlur,
        handleSubmit,
        isSubmitting,
        /* and other goodies */
      }) => (
        <form onSubmit={handleSubmit}>
          <input
            type="email"
            name="email"
            onChange={handleChange}
            onBlur={handleBlur}
            value={values.email}
          />
          {errors.email && touched.email && errors.email}
          <input
            type="password"
            name="password"
            onChange={handleChange}
            onBlur={handleBlur}
            value={values.password}
          />
          {errors.password && touched.password && errors.password}
          <button type="submit" disabled={isSubmitting}>
            Submit
          </button>
        </form>
      )}
    </Formik>
  </div>
);

export default Basic;

Đoạn code trên khá là dài và có thể thấy một số điểm trừ như sau:

  • Bạn phải render Formik component ngay bên trong component của bạn điều này làm cho phần render của bạn tương đối rối rắm.
  • Bạn phải tự map những hàm như handleChange, handleBlur hoặc handleFocus vào thẻ input và công việc này khá tốn thời gian. Tuy nhiên, bạn có thể sử dụng những component của formik như Field hoặc ErrorMessage để save time làm những việc này
  • Formik không tích hợp sẵn validation, bạn tự viết function để validate form values, hoặc sử dụng thêm 1 thư viện như yup để hỗ trợ valiation.
  • Formik không hỗ trợ hook một cách đầy đầy đủ, bạn vẫn có thể sử dụng hook useFormik tuy nhiên khi sử dụng hook này thì những component như Field, ErrorMessage, FieldArray sẽ không hoạt động được.

final-form

final-form được viết bởi tác giả của redux-form do vậy cũng khá là nổi tiếng.

Để tạo 1 form bằng final-form thì bạn sẽ làm như sau:

import React from "react";
import { render } from "react-dom";
import Styles from "./Styles";
import { Form, Field } from "react-final-form";

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const onSubmit = async (values) => {
  await sleep(300);
  window.alert(JSON.stringify(values, 0, 2));
};

const App = () => (
  <Styles>
    <h1>React Final Form Example</h1>
    <h2>Password / Confirm Validation</h2>
    <a
      href="https://final-form.org/react"
      target="_blank"
      rel="noopener noreferrer"
    >
      Read Docs
    </a>
    <Form
      onSubmit={onSubmit}
      validate={(values) => {
        const errors = {};
        if (!values.username) {
          errors.username = "Required";
        }
        if (!values.password) {
          errors.password = "Required";
        }
        if (!values.confirm) {
          errors.confirm = "Required";
        } else if (values.confirm !== values.password) {
          errors.confirm = "Must match";
        }
        return errors;
      }}
      render={({ handleSubmit, form, submitting, pristine, values }) => (
        <form onSubmit={handleSubmit}>
          <Field name="username">
            {({ input, meta }) => (
              <div>
                <label>Username</label>
                <input {...input} type="text" placeholder="Username" />
                {meta.error && meta.touched && <span>{meta.error}</span>}
              </div>
            )}
          </Field>
          <Field name="password">
            {({ input, meta }) => (
              <div>
                <label>Password</label>
                <input {...input} type="password" placeholder="Password" />
                {meta.error && meta.touched && <span>{meta.error}</span>}
              </div>
            )}
          </Field>
          <Field name="confirm">
            {({ input, meta }) => (
              <div>
                <label>Confirm</label>
                <input {...input} type="password" placeholder="Confirm" />
                {meta.error && meta.touched && <span>{meta.error}</span>}
              </div>
            )}
          </Field>
          <div className="buttons">
            <button type="submit" disabled={submitting}>
              Submit
            </button>
            <button
              type="button"
              onClick={form.reset}
              disabled={submitting || pristine}
            >
              Reset
            </button>
          </div>
          <pre>{JSON.stringify(values, 0, 2)}</pre>
        </form>
      )}
    />
  </Styles>
);

render(<App />, document.getElementById("root"));

Theo mình thì cách sử dụng final-form tương tự như formik, tuy nhiên vì sau khi render Form component, bạn bắt buộc phải sử dụng thêm component Field để có thể truyền props của final-form vào các input của bạn.

Vì lý do này nên việc tích hợp final-form với những component khác react-select sẽ hơi khó khăn.

Ngoài ra thì final-form không build-in validation, bạn phải tự viết function để validate dữ liệu của form.

Final-form cũng không hỗ trợ joi hoặc yup, bạn sẽ phải tự workaround để final form có thể sử dụng yup như sau sau: https://github.com/final-form/react-final-form/issues/116

react-hook-form

Mình tìm thấy react hook form tại (/r/reactjs)[https://www.reddit.com/r/reactjs). Dự án hiện tại cũng đang cần xử lý form và mình viết form bằng tay khá mất thời gian nên quyết định dùng react-hook-form thử xem sao.

Cách dùng react-hook-form như sau:

import React from "react";
import { useForm } from "react-hook-form";

export default function App() {
  const { register, handleSubmit, watch, errors } = useForm();
  const onSubmit = data => console.log(data);

  console.log(watch("example")); // watch input value by passing the name of it

  return (
    {/* "handleSubmit" will validate your inputs before invoking "onSubmit" */}
    <form onSubmit={handleSubmit(onSubmit)}>
    {/* register your input into the hook by invoking the "register" function */}
      <input name="example" defaultValue="test" ref={register} />

      {/* include validation with required or other standard HTML validation rules */}
      <input name="exampleRequired" ref={register({ required: true })} />
      {/* errors will return when field validation fails  */}
      {errors.exampleRequired && <span>This field is required</span>}

      <input type="submit" />
    </form>
  );
}

Trong 3 thư viện thì mình thấy react-hook-form dễ dùng nhất. Bạn chỉ cần dùng useForm để tạo form, đồng thời dùng register để đăng ký input với react-hook-form

Khi sử dụng react-hook-form với 1 thư viện form input khác, như react-select cũng rất đơn giản, bạn có thể sử dụng hàm setValue đề truyền giá trị của component đó vào react-hook-form.

React-hook-form cũng có sẵn một số hàm để validate nội dung form như sau:

import React from "react";
import { useForm } from "react-hook-form";

export default function App() {
  const { register, handleSubmit } = useForm();
  const onSubmit = data => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register({ required: true, maxLength: 20 })} />
      <input name="lastName" ref={register({ pattern: /^[A-Za-z]+$/i })} />
      <input name="age" type="number" ref={register({ min: 18, max: 99 })} />
      <input type="submit" />
    </form>
  );
}

Đồng thời bạn cũng có thể dùng 1 thư viện như joy, yup để validate nội dung form.

Về performance

Theo trang chủ của react-hook-form thì react-hook-form có performance tốt nhất do hạn được số lần render component không cần thiết, đồng thời có thời gian mounting thấp nhất.

Tuy nhiên, mình cảm thấy vấn đề re-render đối với form không quan trọng, bời vì hầu hết mỗi form trong React chỉ từ 6 field trở lại. Nếu như bạn có ý định render 100 field thì tốt nhất bạn nên chia form thành nhiều step khác nhau.

Kết luận

react-hook-form là thư viện dễ sử dụng nhất hiện tại, đồng thời có document cực kỳ tốt. Theo mình thấy, điểm hạn chế duy nhất của react-hook-form là chỉ có thể sử dụng với Function component. Do vậy, nếu dự án của bạn vẫn đang sử dụng React phiên bản cũ thì sẽ không sử dụng được.

Tuy nhiên formik vẫn là 1 sự lựa chọn tốt đối với những ai đã sử dụng quen thuộc hoặc là vẫn phải sử dụng class component.

Tác giả: David Tran (david@jslancer.com)

Top comments (2)

Collapse
 
tranduclinh197 profile image
Trần Đức Lĩnh

Cảm ơn tác giả. Mò lắm mới thấy 1 bài viết tiếng Việt

Collapse
 
maxiqboy profile image
Thinh Nguyen

Lần đầu tiên thấy content tiếng Việt trên này, một like ủng hộ bác chủ

This is the first time I see an Vietnamese article on dev.to, great work, thanks