Làm Chủ Conditional Rendering trong React (React Roadmap)

Giới Thiệu: Vì Sao Conditional Rendering Lại Quan Trọng?

Chào mừng bạn trở lại với chuỗi bài viết “React Roadmap”! Sau khi đã cùng nhau khám phá React Là Gì, hiểu về JSX, sự khác biệt giữa Class và Functional Components, và làm chủ cách dữ liệu chảy qua Props và State, đã đến lúc chúng ta tìm hiểu một trong những kỹ thuật cốt lõi giúp giao diện người dùng (UI) của bạn trở nên sống động và phản ứng: Conditional Rendering (Render có điều kiện).

Trong thế giới thực của ứng dụng web, giao diện không phải lúc nào cũng tĩnh. Nó thay đổi dựa trên trạng thái người dùng (đã đăng nhập hay chưa?), dữ liệu đang tải (hiển thị spinner?), kết quả của một hành động (hiển thị thông báo lỗi hay thành công?), hoặc nhiều yếu tố khác. Conditional rendering chính là cách React cho phép bạn hiển thị các phần tử hoặc component khác nhau tùy thuộc vào những điều kiện này.

Hãy tưởng tượng một trang web bán hàng. Bạn chỉ muốn hiển thị nút “Thêm vào giỏ hàng” khi sản phẩm còn hàng. Khi người dùng đã đăng nhập, bạn hiển thị tên của họ thay vì nút “Đăng nhập”. Khi dữ liệu sản phẩm đang được tải từ server, bạn hiển thị một thông báo “Đang tải…”. Tất cả những kịch bản này đều cần đến conditional rendering.

Việc thành thạo conditional rendering không chỉ giúp bạn xây dựng giao diện linh hoạt mà còn tối ưu hóa hiệu suất, tránh render những thứ không cần thiết. Trong bài viết này, chúng ta sẽ đi sâu vào các phương pháp phổ biến để thực hiện conditional rendering trong React, từ cơ bản đến nâng cao, cùng với các ví dụ thực tế.

Các Phương Pháp Conditional Rendering Phổ Biến

React cung cấp nhiều cách để thực hiện conditional rendering. Mỗi phương pháp có ưu và nhược điểm riêng, phù hợp với các tình huống khác nhau. Dưới đây là những kỹ thuật bạn sẽ thường xuyên sử dụng:

1. Sử Dụng If/Else Truyền Thống

Đây là cách đơn giản và trực quan nhất, giống như cách bạn viết JavaScript thông thường. Bạn có thể sử dụng câu lệnh if hoặc if/else trong function component của bạn, trước câu lệnh return, để quyết định phần tử nào sẽ được render.

function Greeting({ isLoggedIn }) {
  if (isLoggedIn) {
    return <h1>Chào mừng trở lại!</h1>;
  } else {
    return <h1>Vui lòng đăng nhập.</h1>;
  }
}

// Ví dụ sử dụng
// <Greeting isLoggedIn={true} />  // Render: <h1>Chào mừng trở lại!</h1>
// <Greeting isLoggedIn={false} /> // Render: <h1>Vui lòng đăng nhập.</h1>

Phương pháp này rất rõ ràng và dễ đọc, đặc biệt khi logic điều kiện phức tạp hoặc bạn cần thực hiện các hành động phụ thuộc vào điều kiện (ví dụ: gọi một hàm, ghi log) trước khi render.

2. Toán Tử Logic && (Short-Circuit Evaluation)

Toán tử logic && (AND) rất hữu ích khi bạn chỉ muốn render một cái gì đó *nếu* điều kiện đúng, và không hiển thị gì cả nếu sai. Trong JavaScript, nếu biểu thức bên trái của && là true, nó sẽ trả về biểu thức bên phải. Nếu biểu thức bên trái là false, nó sẽ trả về false (và dừng lại). React xử lý giá trị false (và các giá trị “falsy” khác như null, undefined, '', 0) bằng cách không render bất cứ thứ gì.

function MessageCounter({ unreadCount }) {
  return (
    <div>
      <h1>Xin chào!</h1>
      {/* Chỉ hiển thị đoạn <p> nếu unreadCount > 0 */}
      {unreadCount > 0 &&
        <p>Bạn có {unreadCount} tin nhắn chưa đọc.</p>
      }
    </div>
  );
}

// Ví dụ sử dụng
// <MessageCounter unreadCount={5} /> // Render: <h1>Xin chào!</h1><p>Bạn có 5 tin nhắn chưa đọc.</p>
// <MessageCounter unreadCount={0} /> // Render: <h1>Xin chào!</h1> (Không có <p>)

Đây là một cách viết rất ngắn gọn và phổ biến để đưa conditional rendering trực tiếp vào trong JSX. Tuy nhiên, bạn cần cẩn thận với các giá trị “falsy” khác false. Ví dụ, nếu unreadCount0, biểu thức unreadCount > 0false, nhưng 0 && <p>...</p> sẽ trả về 0, và React sẽ render số 0 lên UI. Để tránh điều này, hãy đảm bảo biểu thức bên trái && luôn trả về boolean hoặc kiểm tra kỹ lưỡng.

3. Toán Tử Ba Ngôi (Ternary Operator) ? :

Toán tử ba ngôi rất phù hợp khi bạn cần render *một trong hai* phần tử dựa trên điều kiện. Nó có cấu trúc điều_kiện ? giá_trị_nếu_true : giá_trị_nếu_false.

function AuthButton({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn ? (
        <button>Đăng xuất</button>
      ) : (
        <button>Đăng nhập</button>
      )}
    </div>
  );
}

// Ví dụ sử dụng
// <AuthButton isLoggedIn={true} />  // Render: <button>Đăng xuất</button>
// <AuthButton isLoggedIn={false} /> // Render: <button>Đăng nhập</button>

Toán tử ba ngôi cũng rất ngắn gọn và thường được sử dụng inline trong JSX. Nó rõ ràng hơn && khi bạn cần hiển thị một phần tử thay thế khi điều kiện sai. Tuy nhiên, giống như &&, nó có thể trở nên khó đọc nếu bạn lồng nhiều toán tử ba ngôi lại với nhau.

Kỹ Thuật Nâng Cao Hơn: Biến Chứa Phần Tử và Render Null

1. Biến Chứa Phần Tử (Element Variables)

Khi logic conditional rendering của bạn trở nên phức tạp hơn (ví dụ: có nhiều nhánh if/else if/else hoặc cần tính toán trước), việc lưu trữ các phần tử JSX vào biến và sử dụng biến đó trong câu lệnh return là một cách tuyệt vời để giữ cho code dễ đọc.

function UserDashboard({ status, userData }) {
  let content; // Khai báo biến sẽ chứa phần tử render

  if (status === 'loading') {
    content = <p>Đang tải dữ liệu...</p>;
  } else if (status === 'error') {
    content = <p style={{ color: 'red' }}>Đã xảy ra lỗi khi tải dữ liệu.</p>;
  } else if (status === 'success') {
    content = (
      <div>
        <h2>Chào mừng, {userData.name}!</h2>
        <p>Email: {userData.email}</p>
        {/* Có thể thêm conditional rendering khác bên trong đây */}
      </div>
    );
  } else {
    // Trạng thái không xác định
    content = null;
  }

  return (
    <div className="dashboard">
      {content} {/* Render biến chứa phần tử */}
    </div>
  );
}

Phương pháp này giúp tách biệt rõ ràng logic điều kiện và logic render, làm cho component của bạn dễ hiểu và bảo trì hơn, đặc biệt khi phần JSX cần render rất dài hoặc phức tạp.

2. Render Null để Không Hiển Thị Gì

Như đã đề cập ngắn gọn khi nói về toán tử &&, React có một quy ước đặc biệt: khi bạn trả về null (hoặc false, undefined), React sẽ không render bất cứ thứ gì. Điều này rất hữu ích khi bạn có một component mà trong một số trường hợp nhất định, nó không nên hiển thị gì cả.

function WarningBanner({ showWarning }) {
  // Nếu showWarning là false, component này sẽ render null
  if (!showWarning) {
    return null; 
  }

  // Nếu showWarning là true, tiếp tục render banner
  return (
    <div style={{ backgroundColor: 'yellow', padding: '10px' }}>
      Cảnh báo: Bạn đang ở chế độ khách!
    </div>
  );
}

// Ví dụ sử dụng
// <WarningBanner showWarning={true} /> // Render banner
// <WarningBanner showWarning={false} /> // Không render gì cả

Đây là cách tiêu chuẩn để một component “ẩn” chính nó. Hãy nhớ rằng trả về null từ một component là khác với việc hiển thị một component trống. Khi một component trả về null, React tối ưu hóa và không cần tạo các phần tử DOM cho nó.

Xử Lý Nhiều Điều Kiện hoặc Logic Phức Tạp

Khi ứng dụng của bạn có nhiều trạng thái (ví dụ: loading, error, success, empty state…) hoặc logic điều kiện trở nên rất phức tạp, bạn có thể cần các cách tiếp cận có cấu trúc hơn:

1. Chuỗi If/Else If/Else

Như đã thấy trong ví dụ về Element Variables, bạn có thể sử dụng chuỗi if/else if/else truyền thống để xử lý nhiều điều kiện khác nhau. Đây là cách tiếp cận rõ ràng và dễ theo dõi.

function ContentDisplay({ status, data, error }) {
  if (status === 'loading') {
    return <p>Đang tải dữ liệu...</p>;
  } else if (status === 'error') {
    return <p style={{ color: 'red' }}>Lỗi: {error.message}</p>;
  } else if (status === 'success' && data && data.length > 0) {
    return (
      <ul>
        {data.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    );
  } else if (status === 'success' && data && data.length === 0) {
    return <p>Không có dữ liệu để hiển thị.</p>;
  } else {
    // Trạng thái mặc định hoặc không xác định
    return <p>Vui lòng chọn dữ liệu.</p>;
  }
}

Cách này rất dễ hiểu và debug vì nó tuân theo luồng thực thi code JavaScript thông thường. Nó đặc biệt hữu ích khi các điều kiện có sự phụ thuộc hoặc phức tạp.

2. Sử Dụng Object Lookup (Lookup Tables)

Khi bạn có một tập hợp các trạng thái rõ ràng và mỗi trạng thái tương ứng với một phần tử hoặc component cụ thể, sử dụng một object lookup (hay lookup table) có thể làm cho code của bạn rất gọn gàng và dễ mở rộng.

const StatusComponents = {
  'loading': <p>Đang tải...</p>,
  'error': (errorMessage) => <p style={{ color: 'red' }}>Lỗi: {errorMessage}</p>, // Có thể dùng hàm nếu cần prop
  'success': (data) => (
    <ul>
      {data.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  ),
  'empty': <p>Không có dữ liệu.</p>,
  'default': <p>Vui lòng chọn dữ liệu.</p>,
};

function ContentDisplay({ status, data, error }) {
  const ComponentToRender = StatusComponents[status] || StatusComponents['default'];

  // Nếu component là hàm, gọi nó với dữ liệu cần thiết
  if (typeof ComponentToRender === 'function') {
     if (status === 'error') return ComponentToRender(error.message);
     if (status === 'success') return ComponentToRender(data);
     // Xử lý các trường hợp hàm khác nếu có
  }

  // Nếu component là JSX element
  return ComponentToRender;
}

// Hoặc đơn giản hơn nếu các component không cần prop động:
/*
const SimpleStatusComponents = {
  'loading': <p>Đang tải...</p>,
  'error': <p style={{ color: 'red' }}>Đã xảy ra lỗi.</p>,
  'success': <p>Dữ liệu đã sẵn sàng.</p>,
  'default': <p>Trạng thái không xác định.</p>,
};

function SimpleContentDisplay({ status }) {
  return SimpleStatusComponents[status] || SimpleStatusComponents['default'];
}
*/

Cách này đặc biệt hữu ích khi số lượng trạng thái là hữu hạn và mỗi trạng thái có một giao diện hiển thị riêng biệt. Nó giúp code của bạn rất dễ quản lý và mở rộng khi bạn thêm các trạng thái mới.

Conditional Rendering Với Danh Sách (Mapping)

Khi làm việc với danh sách dữ liệu (ví dụ: hiển thị một danh sách các mặt hàng), bạn có thể cần render có điều kiện *bên trong* hàm map hoặc render toàn bộ danh sách *nếu* danh sách đó không rỗng.

1. Rendering có điều kiện từng mục trong danh sách

Bạn có thể sử dụng toán tử && hoặc toán tử ba ngôi bên trong hàm map để chỉ hiển thị các mục thỏa mãn điều kiện.

function ItemList({ items }) {
  return (
    <ul>
      {items.map(item => (
        // Ví dụ 1: Chỉ hiển thị item nếu isActive là true
        item.isActive && <li key={item.id}>{item.name}</li>

        // Ví dụ 2: Dùng ternary nếu cần hiển thị cái khác khi không active (ví dụ: null để không hiển thị gì)
        // item.isActive ? <li key={item.id}>{item.name}</li> : null
      ))}
    </ul>
  );
}

Cách này cho phép bạn lọc hoặc thay đổi cách hiển thị của từng phần tử trong danh sách một cách linh hoạt.

2. Rendering toàn bộ danh sách có điều kiện

Bạn có thể kiểm tra xem danh sách có dữ liệu hay không trước khi render toàn bộ danh sách. Điều này thường được thực hiện với toán tử ba ngôi hoặc &&.

function DisplayList({ data }) {
  return (
    <div>
      {/* Kiểm tra nếu có dữ liệu VÀ data có phần tử */}
      {data && data.length > 0 ? (
        <ul>
          {data.map(item => <li key={item.id}>{item.name}</li>)}
        </ul>
      ) : (
        // Hiển thị thông báo khi không có dữ liệu
        <p>Không có dữ liệu để hiển thị.</p>
      )}
    <div>
  );
}

Điều này giúp bạn hiển thị một thông báo thay thế (ví dụ: “Không có kết quả”, “Giỏ hàng trống”) khi không có dữ liệu để hiển thị danh sách.

Chọn Phương Pháp Nào?

Việc lựa chọn phương pháp conditional rendering phù hợp phụ thuộc vào độ phức tạp của logic, số lượng các nhánh điều kiện, và sở thích cá nhân về độ rõ ràng/ngắn gọn của code. Dưới đây là bảng tóm tắt giúp bạn đưa ra quyết định:

Phương Pháp Trường Hợp Sử Dụng Tối Ưu Ưu Điểm Lưu Ý
if/else (trước return) Logic phức tạp, nhiều điều kiện lồng nhau. Khi cần thực hiện các hành động (ví dụ: gọi hàm, tính toán) trước khi render. Render các tập hợp phần tử khác nhau. Rõ ràng, dễ đọc với logic phức tạp. Tuân thủ luồng code JS thông thường. Dễ debug. Không thể sử dụng trực tiếp trong JSX (phải ở ngoài return).
Toán tử Logic && Render một phần tử hoặc không gì cả (hiển thị hoặc ẩn). Ngắn gọn, dùng được trực tiếp trong JSX. Phù hợp cho các trường hợp đơn giản. Chỉ hiệu quả khi điều kiện đúng thì hiển thị, sai thì không. Cẩn thận với giá trị “falsy” khác false (như 0, "") có thể bị render ngoài ý muốn.
Toán tử Ba Ngôi ? : Render một trong hai phần tử (lựa chọn giữa A hoặc B). Ngắn gọn, dùng được trực tiếp trong JSX. Tốt cho các lựa chọn nhị phân. Có thể trở nên khó đọc với logic lồng nhau hoặc quá dài.
Biến Chứa Phần Tử (Element Variables) Logic phức tạp với nhiều nhánh render khác nhau. Khi muốn tách biệt logic điều kiện khỏi JSX. Tách biệt logic và render. Dễ đọc hơn if/else lồng nhau trong return. Giúp giữ cho JSX sạch sẽ. Thêm một biến trung gian.
Object Lookup Nhiều trạng thái khác nhau, mỗi trạng thái tương ứng với một phần tử/component cụ thể. Logic map trạng thái với UI rất rõ ràng. Rất rõ ràng và dễ mở rộng khi có nhiều trạng thái. Tách biệt dữ liệu/map với logic render. Dễ quản lý các case. Hơi cồng kềnh cho logic đơn giản. Phù hợp nhất khi các “đáp án” của điều kiện là cố định hoặc dễ map.
Render null Khi một component không cần hiển thị bất cứ thứ gì dựa trên điều kiện. Cách chuẩn để “ẩn” toàn bộ component. Tối ưu hiệu suất (không tạo DOM). Chỉ áp dụng khi component là cấp cao nhất quyết định không render.

Hầu hết thời gian, bạn sẽ sử dụng kết hợp các phương pháp này trong các component của mình. Bắt đầu với cách đơn giản nhất phù hợp với nhu cầu, và chuyển sang các kỹ thuật phức tạp hơn khi logic yêu cầu.

Kết Luận

Mastering Conditional Rendering là một kỹ năng cốt lõi trên Lộ trình học React 2025. Nó cho phép bạn xây dựng giao diện người dùng linh hoạt, phản ứng nhanh chóng với những thay đổi về dữ liệu (Props và State) và tương tác của người dùng.

Chúng ta đã khám phá các phương pháp từ cơ bản như if/else, toán tử && và toán tử ba ngôi ? :, đến các kỹ thuật nâng cao hơn như sử dụng biến chứa phần tử, render null, và quản lý logic phức tạp với object lookup.

Điều quan trọng nhất là thực hành. Hãy thử áp dụng các kỹ thuật này vào các component của bạn, tạo ra các giao diện thay đổi dựa trên các điều kiện khác nhau. Bạn sẽ sớm thấy việc xây dựng các ứng dụng React dynamic trở nên dễ dàng hơn rất nhiều.

Trong bài viết tiếp theo của chuỗi React Roadmap, chúng ta sẽ tiếp tục khám phá các khía cạnh quan trọng khác của React. Hãy theo dõi nhé!

Chỉ mục