Mục lục

Logic Placement: Code này nên đặt ở đâu?

Khi giao diện bị chia nhỏ thành nhiều tầng Component con, bạn nên đặt logic Fetch Data, Conditional Rendering, và Business Logic ở đâu? Chiến lược Lift State Up vs Colocation.

Câu hỏi kinh điển: "Tôi có một Page -> Section -> Form -> Input. Tôi nên fetch data ở Page truyền xuống, hay để Input tự fetch?"

Dưới đây là kim chỉ nam cho bạn.

1. Data Fetching: Container vs Leaf

Cách 1: Fetch-then-Render (Truyền thống)

Fetch tại component cha cao nhất (Page). Sau đó truyền props xuống.

  • Ưu điểm: Dễ kiểm soát loading (1 spinner cho cả trang). Tránh "Waterfalls" (fetch nối đuôi).
  • Nhược điểm: Prop Drilling (truyền props qua 5 tầng).

Cách 2: Component Data Colocation (Hiện đại)

Sử dụng thư viện caching như React Query hoặc SWR. Mỗi component tự gọi hook lấy data nó cần.

tsx:
// UserAvatar.tsx
// Không cần cha truyền prop `user`. Tự lấy từ cache.
export function UserAvatar() {
  const { data: user } = useUser(); 
  return <img src={user.avatar} />;
}

Quy tắc: Nếu data được dùng chung bởi nhiều component anh em -> Fetch ở Cha. Nếu data chỉ mình nó dùng -> Fetch tại chỗ (Colocation).

2. Conditional Rendering (Ẩn/Hiện)

Quy tắc: "Cha quyết định sự tồn tại của Con".

tsx:
// ❌ BAD: Con tự quyết định quyền sinh sát
function DeleteButton({ isAdmin }) {
  if (!isAdmin) return null; // Logic ẩn hiện nằm trong con
  return <button>Delete</button>;
}

// ✅ GOOD: Cha quyết định (Inversion of Control)
function UserRow({ user, currentUser }) {
  const canDelete = currentUser.role === 'ADMIN'; // Logic nghiệp vụ ở cha
  
  return (
    <div>
      <UserInfo user={user} />
      {canDelete && <DeleteButton />} {/* Cha quyết định render */}
    </div>
  );
}

Lý do: Component DeleteButton nên ngây thơ (Dumb). Nó chỉ biết hiển thị cái nút. Nhỡ đâu mai sau bạn muốn hiện nút nhưng disable (chứ không ẩn hẳn)? Nếu logic nằm trong con, bạn phải sửa code con.

3. Form State & Validation (Chi tiết)

Vấn đề: Bạn có Form to, chia thành nhiều Section, mỗi Section lại có nhiều Input.

❌ Cách Cũ: Prop Drilling Nightmare

Component cha (Form) nắm register, errors và phải truyền xuống từng cấp.

tsx:
function ParentForm() {
  const { register, formState: { errors } } = useForm();
  return <AddressSection register={register} errors={errors} />; // 😓 Phải truyền
}

function AddressSection({ register, errors }) {
  return <Input {...register("city")} error={errors.city} />; // 😓 Phải nhận
}

✅ Cách Mới: Context-based (Smart Form Integration)

Dùng FormProvider để tạo "vùng phủ sóng". Component con dùng useFormContext để bắt sóng.

tsx:
// 1. Component Con (Tự lo thân mình)
function SmartInput({ name }) {
  // 📡 Tự kết nối với Form cha thông qua Context
  const { register, formState: { errors } } = useFormContext(); 
  
  return (
    <div>
      <input {...register(name)} />
      {errors[name] && <span className="error">{errors[name].message}</span>}
    </div>
  );
}

// 2. Component Cha (Chỉ cần bọc Provider)
function ParentForm() {
  const methods = useForm(); // methods chứa register, handleSubmit...
  
  return (
    <FormProvider {...methods}>
      <form onSubmit={methods.handleSubmit(onSubmit)}>
        {/* 👇 Không cần truyền prop gì cả! */}
        <AddressSection /> 
        <SmartInput name="email" />
      </form>
    </FormProvider>
  );
}

function AddressSection() {
  return (
    <div className="border p-4">
      {/* 👇 Hoạt động ngon lành dù nằm sâu bao nhiêu tầng */}
      <SmartInput name="city" />
      <SmartInput name="street" />
    </div>
  );
}

Lợi ích: Code của AddressSection sạch bong. Bạn có thể di chuyển SmartInput đi bất cứ đâu trong Form mà không phải sửa props của cha/ông nó.

4. Business Logic (Nghiệp vụ)

Đừng viết if-else loằng ngoằng trong JSX. Hãy tách ra Custom Hook.

❌ BAD:

tsx:
function ProductPage() {
  // Logic lẫn lộn UI
  const isDiscount = product.price > 100 && user.vip;
  const finalPrice = isDiscount ? product.price * 0.9 : product.price;
  
  return <div>{finalPrice}</div>;
}

✅ GOOD:

tsx:
// hooks/useProductPrice.ts
function useProductPrice(product, user) {
  const isDiscount = ...;
  const finalPrice = ...;
  return { finalPrice, isDiscount };
}

// UI sạch sẽ
function ProductPage() {
  const { finalPrice } = useProductPrice(product, user);
  return <div>{finalPrice}</div>;
}

Tổng kết

Loại LogicVị trí Đề XuấtTại sao?
Data FetchingPage (Server Component) hoặc React Query CacheTránh Waterfall, tận dụng Cache
Ẩn/Hiện UIComponent Cha (Parent)Inversion of Control
Disable InputComponent Con nhận prop disabledTái sử dụng UI
Validate FormContext (FormProvider)Tránh Prop Drilling
Tính toán giáCustom Hook / UtilsDễ Unit Test
Quảng cáo
mdhorizontal