Peeps Avatar

Hello, codestus.

Go back

Nỗi đau mang tên Prop Drilling

Published at: 11/06/2025

4 mins read

Mọi chuyện bắt đầu rất đơn giản. Một app React nho nhỏ.
Vài component. Một cha, một con.
Ngây thơ, trong sáng.

Rồi yêu cầu xuất hiện...

Người dùng muốn thấy thông tin profile của họ ở header.
Và cả footer.
Và còn ở trong một cái sidebar đệ quy nằm bên trong modal.

Và mình đã làm gì?

Mình truyền props.
Từ App, đến Layout, qua Header, rồi Sidebar, xuống ProfileWidget.

Props, props, props.
Ở khắp nơi. Như hoa giấy trong một đám cưới mà bạn không hề muốn đến.

Kết tuần, mình đang debug một lỗi trong component không cần biết gì về user,
nhưng vẫn phải nhận user như một… shipper không lương,
chỉ để chuyển tiếp cho thằng khác.

Enough is enough.

Mình cần một giải pháp. Một pattern không biến app thành trạm trung chuyển dữ liệu.

Vậy là mình tìm đến React Context, nhưng không phải kiểu mà bạn đang nghĩ đâu nhé.

Context API, Nhưng Khoan Đã...

Ai cũng thích React Context.

Cho tới lúc app bị lag như mở Word trên máy Pentium.

Dùng sai, Context sẽ giết hiệu năng trong im lặng.
Mọi thứ re-render. Cây component nổ tung.
App chạy chậm như nộp hồ sơ hành chính lúc 4h chiều.

Vậy nên, mình làm gì?

Mình gói dữ liệu trong custom hook, sử dụng context có phạm vi rõ ràng, module hóa, và có mục đích cụ thể.

Đây là pattern mà bạn có thể... ăn cắp cũng được.

The Pattern – Cứ Lấy Mà Dùng

Ví dụ bạn đang làm phần xác thực người dùng. Đây là cách mình tổ chức:

UserContext.tsx

import { createContext, useContext, useState } from 'react';

const UserContext = createContext(null);

export const UserProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const login = (userData) => setUser(userData);
  const logout = () => setUser(null);

  return (
    <UserContext.Provider value={{ user, login, logout }}>
      {children}
    </UserContext.Provider>
  );
};

export const useUser = () => useContext(UserContext);

App.tsx

import { UserProvider } from './UserContext';

function App() {
  return (
    <UserProvider>
      <Layout />
    </UserProvider>
  );
}

ProfileWidget.tsx

import { useUser } from './UserContext';

function ProfileWidget() {
  const { user } = useUser();
  return <div>Hello, {user?.name}</div>;
}

Vì Sao Nó Hiệu Quả

  • Không còn prop drilling. Không cần truyền dữ liệu qua 5 cấp component nữa.

  • Logic đóng gói gọn gàng. Muốn xử lý auth? Dùng hook. Vậy là xong.

  • Dễ test. Dễ mock. Tách biệt hoàn toàn.

  • Code sạch, context có scope rõ ràng.

  • Không phải global state vô tổ chức. Mọi thứ có chỗ đứng của nó.

Vậy Redux, Zustand, Recoil thì sao?

Ừ thì… chúng nó vẫn ngon.

Nhưng với 80% ứng dụng ngoài kia, bạn không cần đến bộ quản lý state như thể điều khiển nhà máy điện hạt nhân.

Context + Hook = Gọn. Mạnh. Dễ hiểu.
Như dao Thụy Sĩ, chỉ là không có cái khui rượu vô dụng ấy thôi.

Cảnh Báo Nhẹ Nhàng: Đừng Lạm Dụng

Đừng wrap mọi thứ vào context.

  • Không cần context cho theme, loading state, open modal, mouse position, hay… sinh nhật của con chó nhà bạn.

Chỉ nên dùng Context khi:

Dữ liệu cần được dùng ở nhiều component sâu bên trong
Dữ liệu khá ổn định (không thay đổi mỗi mili-giây)
Có logic đi kèm như login, logout, updateUser

Nếu không? Giữ local state là đủ.

TL;DR - Tóm Gọn Cho Bạn Lướt

  • Prop drilling = hố đen năng suất.

  • Context + custom hooks = cứu rỗi.

  • Context nên rõ ràng, có scope và mục đích.

  • Dùng đúng cách. Đừng làm React giận.

Cuối Cùng: Chọn Sự Tỉnh Táo Thay Vì Khổ Sở

Bạn có thể tiếp tục truyền props như intern chạy deadline,
hoặc bạn có thể tổ chức state như một dev biết quý thời gian và sức khỏe tinh thần.

Mình từng trải qua cả hai. Và mình không quay lại nữa đâu.

Còn bạn?

Chiến lược của bạn để tránh prop drilling là gì?
Mình có bỏ sót pattern nào không? Hay bạn vẫn team Redux năm 2025?

Hãy cùng thảo luận nhé. Comment, đập cái "clap" button.
Hoặc vào inbox... tranh luận nhẹ nhàng với mình.

Chỉ xin bạn đừng truyền props qua 5 cấp component