Chuyển đến nội dung chính

React Hook Form: Xây dựng form component - Part 1

Trong quá trình phát triển các ứng dụng web với ReactJs, việc xây dựng form là một tác vụ không thể tránh khỏi. Tuy nhiên, khi các form trở nên phức tạp với hàng chục field nhập liệu, việc "hardcode" từng <label> và <input> không chỉ tốn thời gian mà còn dẫn đến code lặp lại, khó đọc và khó bảo trì.

Bài viết này sẽ hướng dẫn bạn cách xây dựng một Reusable Form Component, bằng cách tạo ra 1 form, nhưng tách nhiều thành phần trong form ra thành từng component khác nhau.

Cấu trúc Project:

src/
├── types/                      # Thư mục chứa các định nghĩa kiểu dữ liệu (TypeScript interfaces/types)
│   └── formFields.ts 
├── components/                 # Thư mục chứa các UI component
│   └── DynamicForm.tsx         # Component chính của form động
└── App.tsx  

Ví dụ

Trong ví dụ này, mình xây dựng 1 form gồm 2 input là name với gender, với 1 form chính DynamicForm.tsx và 1 form Component (chứa 2 input). Submit button sẽ nằm ở trang Dynamic Form

Định nghĩa Form fields

Định nghĩa cấu trúc field

Chúng ta sẽ định nghĩa các loại input, các property của HTML ở đây
// src/types/formFields.ts
// Định nghĩa các loại input mà form của chúng ta hỗ trợ
export type InputType = 'text' | 'number' | 'select';
export interface FormField {
  label: string;
  name: string;
  type: InputType;
  placeholder: string;
  options?: string[];
}

Định nghĩa kiểu dữ liệu của Form

Định nghĩa cấu trúc dữ liệu tổng thể mà form sẽ trả về sau khi submit
export interface SimpleFormData {
  name: string;
  gender: string;
}

Component form chính

Đây là component React sẽ đọc định nghĩa từ formFields.ts và dùng phương thức map() để tự động render các trường nhập liệu tương ứng. Chúng ta sẽ kết hợp với React Hook Form để xử lý trạng thái form, validation và submit một cách hiệu quả.

Mình sẽ đi từng bước để integrate dễ dàng hơn

Đầu tiên bạn khai báo DyanmicForm
import type { FormField } from '../../types/formFields';
import type { SimpleFormData } from './SimpleFormData';

const DynamicForm = () => {
  // Logic của component sẽ được thêm vào đây
  return (
    <form>
      {/* Giao diện form sẽ được render tại đây */}
      <button type="submit">Submit</button>
    </form>
  );
};

export default DynamicForm;
Khai báo react-hook-form trong DynamicForm.tsx
import { useForm, type SubmitHandler } from 'react-hook-form';
import type { FormField } from '../../types/formFields';
import type { SimpleFormData } from './SimpleFormData';

const DynamicForm = () => {
    const { register, handleSubmit, formState: { errors } } = useForm<SimpleFormData>();
	
	return (
    <form>
      {/* Giao diện form sẽ được render tại đây */}
      <button type="submit">Submit</button>
    </form>
  );
};
export default DynamicForm;

Định nghĩa Dữ liệu Form

fields: FormField[] = [...]: Đây là mảng FormField mà bạn đã định nghĩa. Nó là nguồn dữ liệu để component của chúng ta tự động tạo ra các input trong form.
const fields: FormField[] = [
	{ label: "Name", name: "name", type: "text", placeholder: "Enter your name" },
	{ label: "Gender", name: "gender", type: "select", placeholder: "", options: ["Male", "Female", "Other"] },
];

//Định nghĩa hàm xử lý khi form được submit
const onSubmit: SubmitHandler<SimpleFormData> = (data: SimpleFormData) => {
        console.log("Form submitted:", data);
};
return (
    <form onSubmit={handleSubmit(onSubmit)} className="container mt-5">
      <h2 className="mb-4">Simple Dynamic Form</h2>
      {/* Các field sẽ được render tại đây */}
      <button type="submit" className="btn btn-primary">
        Submit
      </button>
    </form>
  );

onSubmit: SubmitHandler<SimpleFormData> = (data: SimpleFormData) => { ... }: hàm sẽ được gọi khi user nhấn nút submit và form đã pass tất cả các validation rules. Data sẽ là một JavaScript object có cấu trúc giống với SimpleFormData, chứa tất cả các giá trị từ form.

Render Các Trường Form bằng map()

Đây là bước quan trọng nhất, chúng ta chuyển đổi array fields thành các phần tử dynamic UI. Chúng ta sẽ sử dụng phương thức map() của JavaScript.
<form onSubmit={handleSubmit(onSubmit)} className="container mt-5">
      <h2 className="mb-4">Simple Dynamic Form</h2>

      {fields.map((field) => (
        <div className="mb-3" key={field.name}>
          <label className="form-label">{field.label}</label>

          {field.type === "select" ? (
            <select
              {...register(field.name as keyof SimpleFormData, { required: `${field.label} is required` })}
              className="form-select"
            >
              <option value="">-- Select --</option>
              {field.options?.map((opt) => (
                <option key={opt} value={opt}>
                  {opt}
                </option>
              ))}
            </select>
          ) : (
            <input
              type="text"
              placeholder={field.placeholder}
              {...register(field.name as keyof SimpleFormData, { required: `${field.label} is required` })}
              className="form-control"
            />
          )}

          {errors[field.name as keyof SimpleFormData] && (
            <div className="text-danger mt-1">{errors[field.name as keyof SimpleFormData]?.message}</div>
          )}
        </div>
      ))}

      <button type="submit" className="btn btn-primary">
        Submit
      </button>
    </form>
Giải thích:
fields.map((field) => (...))

Đây là phương thức JavaScript để lặp qua từng đối tượng field trong mảng fields.
Với mỗi field, nó sẽ trả về một khối JSX đại diện cho một trường nhập liệu hoàn chỉnh.

key={field.name}: Khi bạn render một danh sách các phần tử trong React, mỗi phần tử được tạo ra bởi map phải có một thuộc tính key duy nhất 

{...register(field.name as keyof SimpleFormData, { required: \${field.label} required` })}`: đăng ký input/select với react-hook-form.

field.name as keyof SimpleFormData: Đảm bảo rằng field name bạn đang đăng ký khớp với các thuộc tính trong SimpleFormData.

{ required: \${field.label} required` }: Đăng ký required rule. Bạn có thể thêm nhiều validation rules khác ở đây (ví dụ: minLength, maxLength, pattern). 

Dưới đây là code toàn bộ của Dynamic Form

import { useForm, type SubmitHandler } from 'react-hook-form';
import type { FormField } from '../../types/formFields';
import type { SimpleFormData } from './SimpleFormData';

const DynamicForm = () => {
    const { register, handleSubmit, formState: { errors } } = useForm<SimpleFormData>();

    const fields: FormField[] = [
        { label: "Name", name: "name", type: "text", placeholder: "Enter your name" },
        { label: "Gender", name: "gender", type: "select", placeholder: "", options: ["Male", "Female", "Other"] },
    ];

    const onSubmit: SubmitHandler<SimpleFormData> = (data: SimpleFormData) => {
        console.log("Form submitted:", data);
    };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="container mt-5">
      <h2 className="mb-4">Simple Dynamic Form</h2>

      {fields.map((field) => (
        <div className="mb-3" key={field.name}>
          <label className="form-label">{field.label}</label>

          {field.type === "select" ? (
            <select
              {...register(field.name as keyof SimpleFormData, { required: `${field.label} is required` })}
              className="form-select"
            >
              <option value="">-- Select --</option>
              {field.options?.map((opt) => (
                <option key={opt} value={opt}>
                  {opt}
                </option>
              ))}
            </select>
          ) : (
            <input
              type="text"
              placeholder={field.placeholder}
              {...register(field.name as keyof SimpleFormData, { required: `${field.label} is required` })}
              className="form-control"
            />
          )}

          {errors[field.name as keyof SimpleFormData] && (
            <div className="text-danger mt-1">{errors[field.name as keyof SimpleFormData]?.message}</div>
          )}
        </div>
      ))}

      <button type="submit" className="btn btn-primary">
        Submit
      </button>
    </form>
  );
}

export default DynamicForm;

Nhận xét

Bài đăng phổ biến từ blog này

[ASP.NET MVC] Authentication và Authorize

Một trong những vấn đề bảo mật cơ bản nhất là đảm bảo những người dùng hợp lệ truy cập vào hệ thống. ASP.NET đưa ra 2 khái niệm: Authentication và Authorize Authentication xác nhận bạn là ai. Ví dụ: Bạn có thể đăng nhập vào hệ thống bằng username và password hoặc bằng ssh. Authorization xác nhận những gì bạn có thể làm. Ví dụ: Bạn được phép truy cập vào website, đăng thông tin lên diễn đàn nhưng bạn không được phép truy cập vào trang mod và admin.

ASP.NET MVC: Cơ bản về Validation

Validation (chứng thực) là một tính năng quan trọng trong ASP.NET MVC và được phát triển trong một thời gian dài. Validation vắng mặt trong phiên bản đầu tiên của asp.net mvc và thật khó để tích hợp 1 framework validation của một bên thứ 3 vì không có khả năng mở rộng. ASP.NET MVC2 đã hỗ trợ framework validation do Microsoft phát triển, tên là Data Annotations. Và trong phiên bản 3, framework validation đã hỗ trợ tốt hơn việc xác thực phía máy khách, và đây là một xu hướng của việc phát triển ứng dụng web ngày nay.

Tổng hợp một số kiến thức lập trình về Amibroker

Giới thiệu về Amibroker Amibroker theo developer Tomasz Janeczko được xây dựng dựa trên ngôn ngữ C. Vì vậy bộ code Amibroker Formula Language sử dụng có syntax khá tương đồng với C, ví dụ như câu lệnh #include để import hay cách gói các object, hàm trong các block {} và kết thúc câu lệnh bằng dấu “;”. AFL trong Amibroker là ngôn ngữ xử lý mảng (an array processing language). Nó hoạt động dựa trên các mảng (các dòng/vector) số liệu, khá giống với cách hoạt động của spreadsheet trên excel.