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

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

Ý tưởng

Trong thực tế, form không chỉ những thành phần riêng lẻ mà gồm nhiều thành phần, như form KYC gồm có thông tin cá nhân, địa chỉ, thông tin liên hệ, financial status, ...

Lấy ví dụ như thông tin cá nhân, được dùng ở 2 page UserInfo cho officer xem và user xem (hoặc nhiều page khác).

Để tái sử dụng, chúng ta tách form thành các thành phần để có thể tái sử dụng.

Trong quá trình mình làm assignment, mình thấy có 2 cách tách form

  • Cách 1: Khai báo props trong từng component con (vd như UserInfo.tsx, AddressInfo.tsx). Trong cách này, mỗi component con tự định nghĩa mảng fields của riêng nó (ví dụ: userInfoFields trong UserInfo.tsx, addressFields trong AddressInfo.tsx).
  • Cách 2: Khai báo tất cả props trong một đối tượng duy nhất ở DynamicForm.tsx (Component cha). Trong cách này, DynamicForm.tsx (dựa trên vd part 1) khai báo biến fieldInfos chứa tất cả các định nghĩa trường trong một mảng fields lớn. Sau đó, fieldInfos sẽ filter các field này và truyền các element phù hợp xuống từng component con.

Ở cách 1, có component sẽ tự quản lý các field của mình. Trong khi đó ở cách 2, chỉ khai báo duy nhất ở Dynamic form, dễ dàng quản lý cấu trúc form.

Mình chọn cách 1.

Xây dựng form component

Dựa vào ví dụ ở bài trước, mình sẽ tách UserInfo ra thành component riêng và khai báo trong DynamicForm. Bạn thử làm với AddressInfo nha.
src/
├── types/
│   ├── formFields.ts
├── components/
│   ├── DynamicForm.tsx
│   ├── UserInfo.tsx
│   └── AddressInfo.tsx
└── App.tsx

Chúng ta sẽ dùng ComplexFormData thay cho SimpleFormData. ComplexFormData sẽ chứa 2 property UserInfo và AddressInfo đại diện cho data của 2 component

// src/types/formFields.ts
export type InputType = 'text' | 'number' | 'select' | 'tel' | 'email' | 'password' | 'textarea' | 'checkbox' | 'radio' | 'date' | 'time' | 'file';
export interface FormField {
  label: string;
  name: string;
  type: InputType;
  placeholder: string;
  options?: string[];
}

export interface UserInfoData{
  firstName: string;
  lastName: string;
  email: string;
  phone: string;
  profileImage: FileList;
}

export interface AddressInfoData {
  street: string;
  city: string;
  zipCode: string;
  country: string;
}

export interface ComplexFormData {
  userInfo: UserInfoData; 
  address: AddressInfoData;
}

Khai báo UserInfo

Chúng ta sẽ đi từng bước để hiểu quá trình khai báo và integrate UserInfo vào DynamicForm

Khai báo UserInfo.tsx
import React from 'react';
import type { UseFormRegister, FieldErrors, Control } from 'react-hook-form';
import type { ComplexFormData } from '../../types/formFields';

interface UserInfoProps {
    register: UseFormRegister<ComplexFormData>;
    errors: FieldErrors<ComplexFormData>;
    control: Control<ComplexFormData>;
}

const UserInfo : React.FC<UserInfoProps> = ({ register, errors, control }) => {
    
    return (<>
        
    </>);
}

export default UserInfo;
Khai báo UserInfo trong DynamicForm.tsx, pass các props register, errors, control xuống cho component UserInfo
import { useForm, type SubmitHandler } from 'react-hook-form';
import type { ComplexFormData } from '../../types/formFields';
import UserInfo from './UserInfo';

const DynamicForm = () => {
    const { register, handleSubmit, control, formState: { errors } } = useForm<ComplexFormData>({
      defaultValues: {
        userInfo: { firstName: '', lastName: '', email: '', phone: '' },
        address: { street: '', city: '', zipCode: '', country: '' },
      }
    });
    const onSubmit: SubmitHandler<ComplexFormData> = (data: ComplexFormData) => {
        console.log("Form submitted:", data);
    };

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

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

export default DynamicForm;
Ở file UserInfo, khai báo các field cần được hiển thị
const userInfoFields: FormField[] = [
        { label: 'Họ', name: 'userInfo.firstName', type: 'text', placeholder: 'Last name' },
        { label: 'Tên', name: 'userInfo.lastName', type: 'text', placeholder: 'First Name' },
        { label: 'Email', name: 'userInfo.email', type: 'email', placeholder: 'email@example.com' },
        { label: 'Số điện thoại', name: 'userInfo.phone', type: 'tel', placeholder: '0123-456-789' },
        { label: 'Ảnh đại diện', name: 'userInfo.profileImage', type: 'file', placeholder: '' },
    ];
Render các field trong userInfoFields
{userInfoFields.map((field) => 
	(
		<div>
			<label htmlFor='{field.name}' className="form-label">{field.label}</label>
			<input id={field.name}
				type={field.type} 
				placeholder={field.placeholder} 
				{...register(field.name as keyof ComplexFormData, { required: `${field.label} is required` })} 
				className="form-control"
				/>
			{errors.userInfo?.[field.name.split('.')[1] as keyof ComplexFormData['userInfo']] && (
						<div className="text-danger mt-1">{errors.userInfo?.[field.name.split('.')[1] as keyof ComplexFormData['userInfo']]?.message}</div>
					  )}
		</div>

	))
}

Dưới đây là toàn bộ source code của DynamicForm và UserInfo

import { useForm, type SubmitHandler } from 'react-hook-form';
Dynamic Form
import type { ComplexFormData } from '../../types/formFields';
import UserInfo from './UserInfo';

const DynamicForm = () => {
    const { register, handleSubmit, control, formState: { errors } } = useForm<ComplexFormData>({
      defaultValues: {
        userInfo: { firstName: '', lastName: '', email: '', phone: '' },
        address: { street: '', city: '', zipCode: '', country: '' },
      }
    });
    const onSubmit: SubmitHandler<ComplexFormData> = (data: ComplexFormData) => {
        console.log("Form submitted:", data);

    const profileImageFile = data.userInfo.profileImage?.[0];
    if (profileImageFile) {
        console.log("Image name:", profileImageFile.name);
        console.log("Size of image:", profileImageFile.size, "bytes");
        console.log("File type:", profileImageFile.type);

        const formData = new FormData();
        formData.append('firstName', data.userInfo.firstName);
        formData.append('lastName', data.userInfo.lastName);
        formData.append('email', data.userInfo.email);
        formData.append('phone', data.userInfo.phone);
        formData.append('street', data.address.street);
        formData.append('city', data.address.city);
        formData.append('zipCode', data.address.zipCode);
        formData.append('country', data.address.country);
        
        if (profileImageFile) {
            formData.append('profileImage', profileImageFile);
        }
        
        try {            
            alert('Data was sent!');
        } catch (error) {
            console.error(error);           
        }

    } 
    };

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

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

export default DynamicForm;
User Info
import React from 'react';
import type { UseFormRegister, FieldErrors, Control } from 'react-hook-form';
import type { ComplexFormData, FormField } from '../../types/formFields';

interface UserInfoProps {
    register: UseFormRegister<ComplexFormData>;
    errors: FieldErrors<ComplexFormData>;
    control: Control<ComplexFormData>;
}

const UserInfo : React.FC<UserInfoProps> = ({ register, errors, control }) => {
    const userInfoFields: FormField[] = [
        { label: 'Họ', name: 'userInfo.firstName', type: 'text', placeholder: 'Nguyễn' },
        { label: 'Tên', name: 'userInfo.lastName', type: 'text', placeholder: 'Văn A' },
        { label: 'Email', name: 'userInfo.email', type: 'email', placeholder: 'email@example.com' },
        { label: 'Số điện thoại', name: 'userInfo.phone', type: 'tel', placeholder: '0123-456-789' },
        { label: 'Ảnh đại diện', name: 'userInfo.profileImage', type: 'file', placeholder: '' },
    ];
    return (<>
        <h2>User Info</h2>
        <div className="card mb-4">
            <div className="card-header bg-info text-white">User Information</div>
            <div className="card-body">
                {userInfoFields.map((field) => 
                    (
                        <div>
                            <label htmlFor='{field.name}' className="form-label">{field.label}</label>
                            <input id={field.name}
                                type={field.type} 
                                placeholder={field.placeholder} 
                                {...register(field.name as keyof ComplexFormData, { required: `${field.label} is required` })} 
                                className="form-control"
                                />
                            {errors.userInfo?.[field.name.split('.')[1] as keyof ComplexFormData['userInfo']] && (
                                        <div className="text-danger mt-1">{errors.userInfo?.[field.name.split('.')[1] as keyof ComplexFormData['userInfo']]?.message}</div>
                                      )}
                        </div>

                    ))
                }
            </div>
        </div>
    </>);
}

export default UserInfo;

 

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.