Skip to content

📅 NGÀY 3: REACT BASICS & JSX - BƯỚC ĐẦU VÀO THỂ GIỚI REACT

📍 Phase 1, Week 1, Day 3 of 75

⏱️ Thời lượng: 3-4 giờ (bao gồm breaks)

🎯 Mục tiêu học tập (5 phút)

Sau bài học hôm nay, bạn sẽ:

  • [ ] Hiểu React là gì và tại sao nó được sử dụng rộng rãi
  • [ ] Tạo được React app đầu tiên và hiểu cấu trúc project
  • [ ] Thành thạo JSX syntax - cách viết UI với JavaScript
  • [ ] Phân biệt rõ JSX vs HTML và biết khi nào dùng gì
  • [ ] Nhúng JavaScript expressions vào JSX một cách chính xác

🤔 Kiểm tra đầu vào (5 phút)

Trước khi bắt đầu, hãy tự hỏi mình:

  1. Arrow functions: const greet = (name) => <h1>Hello {name}</h1> - Bạn hiểu syntax này chứ?
  2. Destructuring: const { name, age } = props - Sẽ dùng nhiều trong React!
  3. Template literals: Có khác gì với JSX expressions không?

💡 Nếu chưa vững về ES6 (Ngày 1-2), hãy ôn lại trước!

📖 PHẦN 1: GIỚI THIỆU KHÁI NIỆM (30 phút)

1.1 Vấn Đề Thực Tế

Scenario: Xây dựng UI truyền thống với vanilla JavaScript

html
<!-- ❌ CÁCH CŨ: Vanilla JavaScript + HTML -->
<!DOCTYPE html>
<html>
  <body>
    <div id="root"></div>

    <script>
      // Tạo UI element by element
      const root = document.getElementById('root');

      const heading = document.createElement('h1');
      heading.textContent = 'Welcome to My App';
      heading.className = 'title';

      const button = document.createElement('button');
      button.textContent = 'Click me';
      button.onclick = function () {
        alert('Clicked!');
      };

      const container = document.createElement('div');
      container.className = 'container';
      container.appendChild(heading);
      container.appendChild(button);

      root.appendChild(container);

      // 😱 Imagine doing this for ENTIRE APP!
    </script>
  </body>
</html>

Vấn đề:

  1. Code dài dòng - Tạo từng element rất verbose
  2. Khó hình dung UI - Không thấy được structure
  3. Khó maintain - Update UI phức tạp
  4. Khó tái sử dụng - Copy-paste code nhiều
  5. Performance issues - Manual DOM manipulation chậm

1.2 Giải Pháp: React & JSX

React giải quyết những vấn đề trên bằng cách:

Declarative UI - Mô tả UI muốn có, React lo việc render
Component-based - Chia nhỏ UI thành các pieces tái sử dụng
JSX Syntax - Viết UI giống HTML ngay trong JavaScript
Virtual DOM - Optimize performance tự động
Ecosystem - Tools, libraries, community lớn

Cùng code với React:

jsx
// ✅ CÁCH MỚI: React + JSX
import React from 'react';

function App() {
  const handleClick = () => {
    alert('Clicked!');
  };

  return (
    <div className='container'>
      <h1 className='title'>Welcome to My App</h1>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
}

// 🎉 Gọn gàng, dễ đọc, dễ maintain!

1.3 Mental Model

┌───────────────────────────────────────────────────────┐
│              REACT ECOSYSTEM                          │
├───────────────────────────────────────────────────────┤
│                                                       │
│  ┌─────────────────────────────────────────────┐      │
│  │         REACT CORE                          │      │
│  │  ┌────────────────────────────────┐         │      │
│  │  │    Components (Functions)      │         │      │
│  │  │    - Return JSX                │         │      │
│  │  │    - Reusable pieces           │         │      │
│  │  └────────────────────────────────┘         │      │
│  │                 ↓                           │      │
│  │  ┌───────────────────────────────────────┐  │      │
│  │  │    JSX (Syntax Extension)             │  │      │
│  │  │    - HTML-like in JavaScript          │  │      │
│  │  │    - Compiled to React.createElement  │  │      │
│  │  └───────────────────────────────────────┘  │      │
│  │                 ↓                           │      │
│  │  ┌────────────────────────────────┐         │      │
│  │  │    Virtual DOM                 │         │      │
│  │  │    - In-memory representation  │         │      │
│  │  │    - Diff & Update efficiently │         │      │
│  │  └────────────────────────────────┘         │      │
│  │                 ↓                           │      │
│  │  ┌────────────────────────────────┐         │      │
│  │  │    Real DOM                    │         │      │
│  │  │    - Browser updates           │         │      │
│  │  │    - User sees UI              │         │      │
│  │  └────────────────────────────────┘         │      │
│  └─────────────────────────────────────────────┘      │
│                                                       │
└───────────────────────────────────────────────────────┘

Analogy dễ hiểu:

🏗️ Xây nhà:

  • Vanilla JS = Đặt từng viên gạch thủ công (tedious!)
  • React Components = Lắp ráp các panels đã làm sẵn (efficient!)
  • JSX = Bản thiết kế (blueprint) dễ đọc
  • Virtual DOM = Mô hình 3D giúp tối ưu việc xây

1.4 Hiểu Lầm Phổ Biến

SAI #1: "JSX là template engine như Handlebars, Pug"
ĐÚNG: JSX là JavaScript. Mọi thứ trong JSX đều là JavaScript code

SAI #2: "JSX là HTML"
ĐÚNG: JSX trông như HTML nhưng có khác biệt (className, onClick, style object...)

SAI #3: "Phải dùng JSX với React"
ĐÚNG: JSX là optional, nhưng 99.9% React devs dùng JSX vì tiện

SAI #4: "React nặng và chậm"
ĐÚNG: React được optimize cho performance, Virtual DOM giúp cập nhật hiệu quả


💻 PHẦN 2: LIVE CODING (45 phút)

Demo 1: Hello React - First Component ⭐

A. Setup React App

bash
# Cách 1: Create React App (CRA) - Recommended for learning
npx create-react-app my-first-app
cd my-first-app
npm start

# Cách 2: Vite (Faster, modern) - Also good
npm create vite@latest my-first-app -- --template react
cd my-first-app
npm install
npm run dev

Cấu trúc project sau khi tạo:

my-first-app/
├── node_modules/       # Dependencies
├── public/             # Static files
│   └── index.html      # HTML template
├── src/
│   ├── App.js          # Root component ⭐ FOCUS HERE
│   ├── index.js        # Entry point
│   └── index.css       # Global styles
├── package.json        # Project config
└── README.md

B. Your First Component

jsx
// ========================================
// FILE: src/App.js
// ========================================

// ✅ STEP 1: Import React (trong React 17+ không bắt buộc nhưng nên có)
import React from 'react';

// ✅ STEP 2: Define Component (Function Component)
function App() {
  // ✅ STEP 3: Return JSX
  return (
    <div>
      <h1>Hello React!</h1>
      <p>This is my first React component</p>
    </div>
  );
}

// ✅ STEP 4: Export Component
export default App;

// 🎓 ANATOMY OF A COMPONENT:
// 1. Function name = Component name (PascalCase)
// 2. Returns JSX (looks like HTML)
// 3. Export để dùng ở nơi khác

Giải thích chi tiết:

jsx
// ========================================
// COMPARING: React Element vs HTML Element
// ========================================

// HTML Element (DOM API):
const element = document.createElement('h1');
element.textContent = 'Hello';
element.className = 'title';

// React Element (JSX):
const element = <h1 className='title'>Hello</h1>;

// 🔍 BEHIND THE SCENES:
// JSX được compile thành:
const element = React.createElement(
  'h1', // type
  { className: 'title' }, // props
  'Hello' // children
);

// ⚠️ BẠN KHÔNG CẦN VIẾT React.createElement!
// Chỉ cần viết JSX, compiler lo việc convert

C. JSX Expressions - Nhúng JavaScript

jsx
// ========================================
// DEMO: JavaScript Expressions trong JSX
// ========================================

function WelcomeMessage() {
  // Variables
  const userName = 'John Doe';
  const currentYear = 2024;

  // Functions
  const getGreeting = () => {
    const hour = new Date().getHours();
    if (hour < 12) return 'Good morning';
    if (hour < 18) return 'Good afternoon';
    return 'Good evening';
  };

  // Objects
  const user = {
    name: 'Jane',
    age: 25,
    role: 'Developer',
  };

  return (
    <div>
      {/* ✅ CÁCH 1: Simple variable */}
      <h1>Welcome, {userName}!</h1>

      {/* ✅ CÁCH 2: Expression */}
      <p>Current year: {currentYear}</p>
      <p>Next year: {currentYear + 1}</p>

      {/* ✅ CÁCH 3: Function call */}
      <p>
        {getGreeting()}, {userName}
      </p>

      {/* ✅ CÁCH 4: Object property access */}
      <p>
        Hello {user.name}, you are {user.age} years old
      </p>

      {/* ✅ CÁCH 5: Ternary operator */}
      <p>You are {user.age >= 18 ? 'an adult' : 'a minor'}</p>

      {/* ✅ CÁCH 6: Logical AND */}
      {user.role === 'Developer' && <span>👨‍💻 Developer Badge</span>}

      {/* ✅ CÁCH 7: Template literal */}
      <p>{`${user.name} is a ${user.role}`}</p>

      {/* ❌ KHÔNG ĐƯỢC: Statements (if, for, while) */}
      {/* {if (user.age > 18) { return <p>Adult</p> }} */}

      {/* ❌ KHÔNG ĐƯỢC: Object trực tiếp */}
      {/* <p>{user}</p> */}
      {/* Error: Objects are not valid as a React child */}
    </div>
  );
}

// 🎓 RULES FOR EXPRESSIONS:
// ✅ Can use: Variables, functions, ternary, logical operators
// ✅ Must return: String, Number, JSX, Array of JSX
// ❌ Cannot use: Statements (if, for, switch)
// ❌ Cannot render: Objects directly (except JSX objects)

📊 So sánh Template Literal vs JSX Expression:

jsx
// Template Literal (ES6)
const html = `
  <div>
    <h1>Hello ${name}</h1>
    <p>Age: ${age}</p>
  </div>
`;
// Returns: String

// JSX Expression (React)
const jsx = (
  <div>
    <h1>Hello {name}</h1>
    <p>Age: {age}</p>
  </div>
);
// Returns: React Element (Virtual DOM node)

Demo 2: JSX vs HTML - Key Differences ⭐⭐

jsx
// ========================================
// DEMO: JSX vs HTML Differences
// ========================================

function JSXDifferencesDemo() {
  const imageUrl = 'https://via.placeholder.com/150';
  const inputValue = 'Hello';

  return (
    <div>
      <h2>JSX vs HTML Differences</h2>
      {/* ========================================
          DIFFERENCE 1: className vs class
          ========================================*/}
      {/* ❌ HTML: */}
      {/* <div class="container"></div> */}
      {/* ✅ JSX: */}
      <div className='container'>
        {/* class là reserved keyword trong JavaScript */}
        {/* JSX dùng className thay vì class */}
      </div>
      {/* ========================================
          DIFFERENCE 2: htmlFor vs for
          ========================================*/}
      {/* ❌ HTML: */}
      {/* <label for="email">Email</label> */}
      {/* ✅ JSX: */}
      <label htmlFor='email'>Email:</label>
      <input
        id='email'
        type='text'
      />
      {/* ========================================
          DIFFERENCE 3: Self-closing tags
          ========================================*/}
      {/* ❌ HTML: Can omit closing */}
      {/* <img src="..."> */}
      {/* <br> */}
      {/* <input type="text"> */}
      {/* ✅ JSX: MUST self-close */}
      <img
        src={imageUrl}
        alt='Placeholder'
      />
      <br />
      <input type='text' />
      {/* ========================================
          DIFFERENCE 4: style attribute
          ========================================*/}
      {/* ❌ HTML: String */}
      {/* <div style="color: red; font-size: 20px;"></div> */}
      {/* ✅ JSX: Object with camelCase */}
      <div
        style={{
          color: 'red', // camelCase
          fontSize: '20px', // fontSize not font-size
          backgroundColor: 'blue', // backgroundColor not background-color
          marginTop: '10px', // marginTop not margin-top
        }}
      >
        Styled text
      </div>
      {/* 💡 TIP: Double braces {{ }} */}
      {/* Outer {} = JSX expression */}
      {/* Inner {} = JavaScript object */}
      {/* ========================================
          DIFFERENCE 5: Event handlers
          ========================================*/}
      {/* ❌ HTML: String, lowercase */}
      {/* <button onclick="handleClick()">Click</button> */}
      {/* ✅ JSX: Function reference, camelCase */}
      <button onClick={() => alert('Clicked!')}>Click me</button>
      {/* ========================================
          DIFFERENCE 6: Comments
          ========================================*/}
      {/* ❌ HTML comment: */}
      {/* <!-- This is HTML comment --> */}
      {/* ✅ JSX comment (inside JSX): */}
      {/* This is JSX comment */}
      {/* ✅ JavaScript comment (outside JSX): */}
      // This is JS comment
      {/* ========================================
          DIFFERENCE 7: Boolean attributes
          ========================================*/}
      {/* ❌ HTML: */}
      {/* <input disabled> */}
      {/* <input checked> */}
      {/* ✅ JSX: Explicit boolean */}
      <input
        type='checkbox'
        disabled={true}
      />
      <input
        type='checkbox'
        checked={false}
      />
      {/* 💡 Shorthand (same as disabled={true}): */}
      <input
        type='text'
        disabled
      />
      {/* ========================================
          DIFFERENCE 8: Multiple root elements
          ========================================*/}
      {/* ❌ INVALID: Multiple root elements */}
      {/* 
      return (
        <h1>Title</h1>
        <p>Paragraph</p>
      );
      */}
      {/* ✅ VALID: Wrap in single parent */}
      {/* 
      return (
        <div>
          <h1>Title</h1>
          <p>Paragraph</p>
        </div>
      );
      */}
      {/* ✅ ALSO VALID: React Fragment */}
      {/* 
      return (
        <>
          <h1>Title</h1>
          <p>Paragraph</p>
        </>
      );
      */}
    </div>
  );
}

export default JSXDifferencesDemo;

📊 Quick Reference Table:

FeatureHTMLJSXReason
Classclass="..."className="..."class is JS reserved word
Label Forfor="..."htmlFor="..."for is JS reserved word
Self-closingOptionalRequiredXML syntax requirement
StyleStringObjectType-safe styling
EventsonclickonClickcamelCase convention
Comments<!-- -->{/* */}Inside JS expressions
Boolean attrsdisableddisabled={true}Explicit boolean
Multiple rootsAllowedNeed wrapperSingle root rule

Demo 3: JSX Edge Cases & Best Practices ⭐⭐⭐

jsx
// ========================================
// DEMO: JSX Edge Cases
// ========================================

function JSXEdgeCases() {
  const items = ['Apple', 'Banana', 'Cherry'];
  const emptyArray = [];
  const user = { name: 'John', age: 25 };
  const nullValue = null;
  const undefinedValue = undefined;
  const booleanValue = true;
  const numberValue = 0;

  return (
    <div>
      <h2>JSX Edge Cases</h2>

      {/* ========================================
          CASE 1: Rendering null, undefined, boolean
          ========================================*/}

      <p>Null: {nullValue}</p>
      {/* Renders: Null:  (nothing) */}

      <p>Undefined: {undefinedValue}</p>
      {/* Renders: Undefined:  (nothing) */}

      <p>Boolean: {booleanValue}</p>
      {/* Renders: Boolean:  (nothing) */}

      <p>Number zero: {numberValue}</p>
      {/* Renders: Number zero: 0 */}

      {/* 🎓 RULE: null, undefined, boolean are ignored */}
      {/* Only numbers are rendered (including 0) */}

      {/* ========================================
          CASE 2: Rendering arrays
          ========================================*/}

      {/* ✅ Array of strings/numbers */}
      <p>Items: {items}</p>
      {/* Renders: Items: AppleBananaCherry */}

      {/* ✅ Array of JSX elements */}
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>

      {/* ⚠️ Empty array */}
      <p>Empty: {emptyArray}</p>
      {/* Renders: Empty:  (nothing) */}

      {/* ========================================
          CASE 3: Conditional rendering patterns
          ========================================*/}

      {/* ✅ PATTERN 1: Ternary */}
      {items.length > 0 ? <p>We have {items.length} items</p> : <p>No items</p>}

      {/* ✅ PATTERN 2: Logical AND */}
      {items.length > 0 && <p>Items available!</p>}

      {/* ❌ PITFALL: Falsy number */}
      {items.length && <p>Has items</p>}
      {/* If items.length = 0, renders "0" */}

      {/* ✅ FIX: Explicit boolean */}
      {items.length > 0 && <p>Has items</p>}

      {/* ✅ PATTERN 3: Nullish coalescing for defaults */}
      <p>Count: {items.length ?? 'N/A'}</p>

      {/* ========================================
          CASE 4: Rendering objects
          ========================================*/}

      {/* ❌ INVALID: Direct object rendering */}
      {/* <p>User: {user}</p> */}
      {/* Error: Objects are not valid as a React child */}

      {/* ✅ VALID: Access properties */}
      <p>
        User: {user.name}, Age: {user.age}
      </p>

      {/* ✅ VALID: JSON stringify for debugging */}
      <pre>{JSON.stringify(user, null, 2)}</pre>

      {/* ========================================
          CASE 5: Whitespace handling
          ========================================*/}

      {/* JSX removes whitespace between lines */}
      <p>Hello World</p>
      {/* Renders: HelloWorld (no space!) */}

      {/* ✅ FIX 1: Explicit space */}
      <p>Hello World</p>

      {/* ✅ FIX 2: Template literal */}
      <p>{`Hello World`}</p>

      {/* ✅ FIX 3: Multiple strings */}
      <p>
        {'Hello'} {'World'}
      </p>

      {/* ========================================
          CASE 6: Reserved words
          ========================================*/}

      {/* ❌ INVALID: JavaScript reserved words */}
      {/* <div class="box"></div> */}
      {/* <label for="name"></label> */}

      {/* ✅ VALID: Use JSX equivalents */}
      <div className='box'></div>
      <label htmlFor='name'></label>

      {/* ========================================
          CASE 7: Escape HTML entities
          ========================================*/}

      {/* ❌ Will render as text: */}
      <p>&lt;div&gt; tag</p>
      {/* Renders: &lt;div&gt; tag (literally) */}

      {/* ✅ Use actual characters: */}
      <p>{'<div>'} tag</p>
      {/* Renders: <div> tag */}

      {/* ✅ Or use Unicode: */}
      <p>
        {'\u003C'}div{'\u003E'} tag
      </p>
    </div>
  );
}

// ========================================
// BEST PRACTICES
// ========================================

function BestPractices() {
  const isLoggedIn = true;
  const userRole = 'admin';
  const items = [1, 2, 3];

  return (
    <div>
      {/* ✅ PRACTICE 1: Explicit conditionals */}
      {isLoggedIn === true && <p>Welcome back!</p>}
      {/* Better than: {isLoggedIn && ...} */}

      {/* ✅ PRACTICE 2: Guard against 0 */}
      {items.length > 0 && <p>Has items</p>}
      {/* NOT: {items.length && ...} */}

      {/* ✅ PRACTICE 3: Use fragments for no extra DOM */}
      <>
        <h1>Title</h1>
        <p>Content</p>
      </>
      {/* Instead of: <div>...</div> */}

      {/* ✅ PRACTICE 4: Extract complex expressions */}
      {(() => {
        if (userRole === 'admin') return <p>Admin Panel</p>;
        if (userRole === 'user') return <p>User Dashboard</p>;
        return <p>Guest View</p>;
      })()}

      {/* ✅ PRACTICE 5: Use variables for readability */}
      {(() => {
        const isAdmin = userRole === 'admin';
        const isUser = userRole === 'user';

        return (
          <>
            {isAdmin && <p>Admin Panel</p>}
            {isUser && <p>User Dashboard</p>}
          </>
        );
      })()}

      {/* ✅ PRACTICE 6: Comments for complex logic */}
      {/* 
        Show admin panel only for:
        - Logged in users
        - With admin role
        - On weekdays
      */}
      {isLoggedIn && userRole === 'admin' && <p>Admin Panel</p>}
    </div>
  );
}

🔨 PHẦN 3: BÀI TẬP THỰC HÀNH (60 phút)

⭐ Exercise 1: JSX Basics (15 phút)

jsx
/**
 * 🎯 Mục tiêu: Tạo React component đầu tiên với JSX
 * ⏱️ Thời gian: 15 phút
 * 🚫 KHÔNG dùng: Props, State, Events (chưa học!)
 *
 * Requirements:
 * 1. Tạo function component tên "UserProfile"
 * 2. Hiển thị thông tin user với JSX
 * 3. Sử dụng JavaScript expressions
 * 4. Follow JSX rules (className, style object, etc.)
 *
 * 💡 Gợi ý: Sử dụng variables và expressions
 */

// ❌ CÁCH SAI: Hardcode values
function UserProfileWrong() {
  return (
    <div>
      <h1>John Doe</h1>
      <p>Age: 25</p>
      <p>Email: john@example.com</p>
    </div>
  );
  // Problem: Không flexible, phải edit JSX mỗi lần change data
}

// ✅ CÁCH ĐÚNG: Use variables và expressions
function UserProfileCorrect() {
  // Define data
  const user = {
    firstName: 'John',
    lastName: 'Doe',
    age: 25,
    email: 'john@example.com',
    role: 'Developer',
    isActive: true,
  };

  // Calculated values
  const fullName = `${user.firstName} ${user.lastName}`;
  const birthYear = new Date().getFullYear() - user.age;

  return (
    <div className='user-profile'>
      {/* Use expressions */}
      <h1>{fullName}</h1>
      <p>
        Age: {user.age} (Born in {birthYear})
      </p>
      <p>Email: {user.email}</p>
      <p>Role: {user.role}</p>

      {/* Conditional rendering */}
      <p>Status: {user.isActive ? '🟢 Active' : '🔴 Inactive'}</p>

      {/* Style object */}
      <div
        style={{
          padding: '20px',
          backgroundColor: user.isActive ? '#e8f5e9' : '#ffebee',
          borderRadius: '8px',
        }}
      >
        Profile card
      </div>
    </div>
  );
}

// 🎯 NHIỆM VỤ CỦA BẠN:

/**
 * TODO 1: Tạo component "ProductCard"
 * Display:
 * - Product name
 * - Price (formatted as $XX.XX)
 * - Discount percentage (if any)
 * - Final price after discount
 * - Stock status (In Stock / Out of Stock)
 */

function ProductCard() {
  const product = {
    name: 'Wireless Headphones',
    price: 99.99,
    discount: 0.15, // 15%
    inStock: true,
  };

  // YOUR CODE HERE
  // Calculate:
  // - discountAmount
  // - finalPrice
  // - Display with proper formatting
}

/**
 * TODO 2: Tạo component "WeatherWidget"
 * Display:
 * - City name
 * - Temperature (in Celsius and Fahrenheit)
 * - Weather condition with emoji
 * - Last updated time
 */

function WeatherWidget() {
  const weather = {
    city: 'San Francisco',
    tempCelsius: 20,
    condition: 'sunny', // sunny, cloudy, rainy
    lastUpdated: new Date(),
  };

  // YOUR CODE HERE
  // Calculate:
  // - tempFahrenheit = (tempCelsius * 9/5) + 32
  // - weatherEmoji based on condition
  // - formatted time
}

/**
 * TODO 3: Tạo component "BlogPost"
 * Display:
 * - Title
 * - Author name
 * - Published date (formatted)
 * - Reading time
 * - Tags list
 * - Content preview (first 100 characters)
 */

function BlogPost() {
  const post = {
    title: 'Getting Started with React',
    author: 'Jane Smith',
    publishedDate: new Date('2024-01-15'),
    readingTime: 5, // minutes
    tags: ['React', 'JavaScript', 'Tutorial'],
    content:
      'React is a powerful JavaScript library for building user interfaces. In this tutorial, we will explore the fundamentals of React and learn how to create your first component. React makes it easy to create interactive UIs by breaking them down into reusable components.',
  };

  // YOUR CODE HERE
  // Calculate/Format:
  // - Format date as 'Jan 15, 2024'
  // - Content preview (first 100 chars + '...')
  // - Display tags as comma-separated list
  // - Show reading time with proper styling
}

// 📝 Expected Output:
// ProductCard: Should show price, discount, final price with $ symbol
// WeatherWidget: Should show both °C and °F, emoji for condition
// BlogPost: Should show formatted date, tags, content preview
💡 Solution
jsx
import React from 'react';

/**
 * ✅ SOLUTION: Exercise 1 - JSX Basics
 */

// TODO 1: ProductCard
function ProductCard() {
  const product = {
    name: 'Wireless Headphones',
    price: 99.99,
    discount: 0.15, // 15%
    inStock: true,
  };

  // Calculations
  const discountAmount = product.price * product.discount;
  const finalPrice = product.price - discountAmount;
  const discountPercent = product.discount * 100;

  return (
    <div
      style={{
        border: '1px solid #ddd',
        borderRadius: '8px',
        padding: '20px',
        margin: '20px',
        maxWidth: '300px',
      }}
    >
      <h2 style={{ margin: '0 0 10px 0' }}>{product.name}</h2>

      <p
        style={{
          textDecoration: product.discount > 0 ? 'line-through' : 'none',
          color: '#888',
        }}
      >
        Original: ${product.price.toFixed(2)}
      </p>

      {product.discount > 0 && (
        <p style={{ color: '#e74c3c', fontWeight: 'bold' }}>
          {discountPercent}% OFF - Save ${discountAmount.toFixed(2)}
        </p>
      )}

      <p style={{ fontSize: '24px', fontWeight: 'bold', color: '#27ae60' }}>
        ${finalPrice.toFixed(2)}
      </p>

      <p
        style={{
          padding: '8px 16px',
          backgroundColor: product.inStock ? '#d4edda' : '#f8d7da',
          color: product.inStock ? '#155724' : '#721c24',
          borderRadius: '4px',
          textAlign: 'center',
          fontWeight: 'bold',
        }}
      >
        {product.inStock ? '✅ In Stock' : '❌ Out of Stock'}
      </p>
    </div>
  );
}

// TODO 2: WeatherWidget
function WeatherWidget() {
  const weather = {
    city: 'San Francisco',
    tempCelsius: 20,
    condition: 'sunny', // sunny, cloudy, rainy
    lastUpdated: new Date(),
  };

  // Calculations
  const tempFahrenheit = Math.round((weather.tempCelsius * 9) / 5 + 32);

  // Weather emoji mapping
  const weatherEmojis = {
    sunny: '☀️',
    cloudy: '☁️',
    rainy: '🌧️',
  };

  const weatherEmoji = weatherEmojis[weather.condition] || '🌤️';

  // Format time
  const formattedTime = weather.lastUpdated.toLocaleTimeString('en-US', {
    hour: '2-digit',
    minute: '2-digit',
  });

  return (
    <div
      style={{
        background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
        color: 'white',
        borderRadius: '16px',
        padding: '30px',
        margin: '20px',
        maxWidth: '300px',
        textAlign: 'center',
      }}
    >
      <h2 style={{ margin: '0 0 20px 0', fontSize: '28px' }}>{weather.city}</h2>

      <div style={{ fontSize: '80px', margin: '20px 0' }}>{weatherEmoji}</div>

      <div style={{ fontSize: '48px', fontWeight: 'bold', margin: '20px 0' }}>
        {weather.tempCelsius}°C
      </div>

      <p style={{ fontSize: '18px', opacity: 0.9 }}>({tempFahrenheit}°F)</p>

      <p
        style={{
          textTransform: 'capitalize',
          fontSize: '20px',
          margin: '15px 0',
        }}
      >
        {weather.condition}
      </p>

      <p style={{ fontSize: '14px', opacity: 0.8, marginTop: '20px' }}>
        Last updated: {formattedTime}
      </p>
    </div>
  );
}

// TODO 3: BlogPost
function BlogPost() {
  const post = {
    title: 'Getting Started with React',
    author: 'Jane Smith',
    publishedDate: new Date('2024-01-15'),
    readingTime: 5, // minutes
    tags: ['React', 'JavaScript', 'Tutorial'],
    content:
      'React is a powerful JavaScript library for building user interfaces. In this tutorial, we will explore the fundamentals of React and learn how to create your first component. React makes it easy to create interactive UIs by breaking them down into reusable components.',
  };

  // Format date
  const formattedDate = post.publishedDate.toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'short',
    day: 'numeric',
  });

  // Content preview
  const contentPreview =
    post.content.length > 100
      ? post.content.substring(0, 100) + '...'
      : post.content;

  return (
    <article
      style={{
        border: '1px solid #e0e0e0',
        borderRadius: '8px',
        padding: '24px',
        margin: '20px',
        maxWidth: '600px',
        backgroundColor: '#fff',
      }}
    >
      <h1
        style={{
          margin: '0 0 12px 0',
          fontSize: '28px',
          color: '#1a1a1a',
        }}
      >
        {post.title}
      </h1>

      <div
        style={{
          display: 'flex',
          gap: '16px',
          marginBottom: '16px',
          color: '#666',
          fontSize: '14px',
        }}
      >
        <span>By {post.author}</span>
        <span>•</span>
        <span>{formattedDate}</span>
        <span>•</span>
        <span>{post.readingTime} min read</span>
      </div>

      <div style={{ marginBottom: '16px' }}>
        {post.tags.map((tag, index) => (
          <span
            key={index}
            style={{
              display: 'inline-block',
              backgroundColor: '#e3f2fd',
              color: '#1976d2',
              padding: '4px 12px',
              borderRadius: '16px',
              marginRight: '8px',
              fontSize: '12px',
              fontWeight: '500',
            }}
          >
            #{tag}
          </span>
        ))}
      </div>

      <p
        style={{
          lineHeight: '1.6',
          color: '#444',
          fontSize: '16px',
        }}
      >
        {contentPreview}
      </p>

      <a
        href='#'
        style={{
          color: '#1976d2',
          textDecoration: 'none',
          fontWeight: '500',
          fontSize: '14px',
        }}
      >
        Read more →
      </a>
    </article>
  );
}

// Main App Component
export default function App() {
  return (
    <div
      style={{
        fontFamily:
          '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
        backgroundColor: '#f5f5f5',
        minHeight: '100vh',
        padding: '20px',
      }}
    >
      <h1 style={{ textAlign: 'center', color: '#333' }}>
        Exercise 1: JSX Basics Solutions
      </h1>

      <ProductCard />
      <WeatherWidget />
      <BlogPost />
    </div>
  );
}

🎓 Kiến thức trọng tâm:

  • Sử dụng biểu thức JSX với dấu { }
  • Inline style dưới dạng object JavaScript
  • Render có điều kiện bằng toán tử ternary
  • Dùng template literals để định dạng chuỗi
  • Render danh sách bằng Array.map()
  • Tính toán và biến đổi dữ liệu trước khi render

⭐⭐ Exercise 2: JSX vs HTML Conversion (25 phút)

jsx
/**
 * 🎯 Mục tiêu: Chuyển đổi HTML sang JSX đúng cách
 * ⏱️ Thời gian: 25 phút
 *
 * Scenario: Bạn có HTML template cần convert sang React component
 *
 * 🤔 PHÂN TÍCH:
 * Approach A: Copy-paste HTML trực tiếp vào JSX
 * Pros: Nhanh
 * Cons: Sẽ có lỗi syntax (class, for, style...)
 *
 * Approach B: Convert từng phần, fix syntax issues
 * Pros: Hiểu rõ differences, code chạy đúng
 * Cons: Mất thời gian hơn
 *
 * 💭 CHỌN APPROACH B - Learn by doing
 */

// ========================================
// HTML TEMPLATE - CẦN CONVERT
// ========================================

const htmlTemplate = `
<div class="login-form">
  <form onsubmit="handleSubmit(event)">
    <h2 class="form-title">Login</h2>
    
    <div class="form-group">
      <label for="email">Email Address</label>
      <input 
        type="email" 
        id="email" 
        name="email"
        placeholder="Enter your email"
        required
      >
    </div>
    
    <div class="form-group">
      <label for="password">Password</label>
      <input 
        type="password" 
        id="password" 
        name="password"
        placeholder="Enter your password"
        required
      >
    </div>
    
    <div class="form-group">
      <input type="checkbox" id="remember" name="remember">
      <label for="remember">Remember me</label>
    </div>
    
    <button type="submit" class="btn-primary">
      Login
    </button>
    
    <p class="form-footer">
      Don't have an account? 
      <a href="/signup" class="link">Sign up</a>
    </p>
  </form>
</div>
`;

// TODO 1: Convert HTML trên sang JSX component
// Identify và fix tất cả JSX syntax issues

function LoginForm() {
  // YOUR CODE HERE
  // Issues cần fix:
  // - class → className
  // - for → htmlFor
  // - onsubmit → onSubmit
  // - Self-closing tags
  // - Event handler format
  // - Any other JSX rules
}

// ========================================
// HTML TEMPLATE 2 - WITH INLINE STYLES
// ========================================

const htmlWithStyles = `
<div class="card" style="padding: 20px; background-color: white; border-radius: 8px;">
  <img src="avatar.jpg" alt="User avatar" style="width: 100px; height: 100px; border-radius: 50%;">
  
  <h3 style="margin-top: 10px; font-size: 24px; color: #333;">John Doe</h3>
  
  <p style="color: #666; font-size: 14px;">Software Developer</p>
  
  <div style="margin-top: 20px; display: flex; gap: 10px;">
    <button onclick="followUser()" style="padding: 8px 16px; background-color: #3b82f6; color: white; border: none; border-radius: 4px;">
      Follow
    </button>
    
    <button onclick="messageUser()" style="padding: 8px 16px; background-color: white; color: #3b82f6; border: 1px solid #3b82f6; border-radius: 4px;">
      Message
    </button>
  </div>
</div>
`;

// TODO 2: Convert HTML with inline styles sang JSX
// Convert style attribute thành style object

function UserCard() {
  // YOUR CODE HERE
  // Fix:
  // - style string → style object
  // - CSS properties to camelCase (background-color → backgroundColor)
  // - onclick → onClick
  // - Other JSX issues
}

// ========================================
// HTML TEMPLATE 3 - COMPLEX STRUCTURE
// ========================================

const complexHTML = `
<nav class="navbar">
  <div class="nav-brand">
    <img src="logo.png" alt="Logo">
    <span>MyApp</span>
  </div>
  
  <ul class="nav-menu">
    <li class="nav-item active">
      <a href="/" class="nav-link">Home</a>
    </li>
    <li class="nav-item">
      <a href="/about" class="nav-link">About</a>
    </li>
    <li class="nav-item">
      <a href="/contact" class="nav-link">Contact</a>
    </li>
  </ul>
  
  <div class="nav-actions">
    <input type="search" placeholder="Search..." class="search-input">
    <button class="btn-search">Search</button>
  </div>
</nav>
`;

// TODO 3: Convert complex navigation HTML sang JSX
function Navbar() {
  // YOUR CODE HERE
  // Consider:
  // - Multiple className fixes
  // - Self-closing tags
  // - Structure preservation
  // - Semantic meaning
}

// 📝 VALIDATION CHECKLIST:
// - [ ] All 'class' converted to 'className'
// - [ ] All 'for' converted to 'htmlFor'
// - [ ] All inline styles are objects with camelCase
// - [ ] All self-closing tags have />
// - [ ] All event handlers are camelCase (onClick, onSubmit)
// - [ ] No syntax errors when rendered
💡 Solution
jsx
import React from 'react';

/**
 * ✅ SOLUTION: Exercise 2 - HTML to JSX Conversion
 */

// TODO 1: LoginForm
function LoginForm() {
  const handleSubmit = (event) => {
    event.preventDefault();
    alert('Form submitted! (This is a demo)');
  };

  return (
    <div
      style={{
        maxWidth: '400px',
        padding: '32px',
        backgroundColor: 'white',
        borderRadius: '8px',
        boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
        margin: '20px',
      }}
    >
      <div>
        <h2
          style={{
            marginTop: 0,
            marginBottom: '24px',
            textAlign: 'center',
            color: '#1f2937',
          }}
        >
          Login
        </h2>

        <div style={{ marginBottom: '16px' }}>
          <label
            htmlFor='email'
            style={{
              display: 'block',
              marginBottom: '8px',
              fontWeight: '500',
              color: '#374151',
            }}
          >
            Email Address
          </label>
          <input
            type='email'
            id='email'
            name='email'
            placeholder='Enter your email'
            required
            style={{
              width: '100%',
              padding: '10px 12px',
              border: '1px solid #d1d5db',
              borderRadius: '4px',
              fontSize: '14px',
              boxSizing: 'border-box',
            }}
          />
        </div>

        <div style={{ marginBottom: '16px' }}>
          <label
            htmlFor='password'
            style={{
              display: 'block',
              marginBottom: '8px',
              fontWeight: '500',
              color: '#374151',
            }}
          >
            Password
          </label>
          <input
            type='password'
            id='password'
            name='password'
            placeholder='Enter your password'
            required
            style={{
              width: '100%',
              padding: '10px 12px',
              border: '1px solid #d1d5db',
              borderRadius: '4px',
              fontSize: '14px',
              boxSizing: 'border-box',
            }}
          />
        </div>

        <div
          style={{
            marginBottom: '20px',
            display: 'flex',
            alignItems: 'center',
            gap: '8px',
          }}
        >
          <input
            type='checkbox'
            id='remember'
            name='remember'
          />
          <label
            htmlFor='remember'
            style={{ fontSize: '14px', color: '#6b7280' }}
          >
            Remember me
          </label>
        </div>

        <button
          type='button'
          onClick={handleSubmit}
          style={{
            width: '100%',
            padding: '12px',
            backgroundColor: '#3b82f6',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            fontSize: '16px',
            fontWeight: '500',
            cursor: 'pointer',
          }}
        >
          Login
        </button>

        <p
          style={{
            marginTop: '20px',
            textAlign: 'center',
            fontSize: '14px',
            color: '#6b7280',
          }}
        >
          Don't have an account?{' '}
          <a
            href='#signup'
            style={{
              color: '#3b82f6',
              textDecoration: 'none',
              fontWeight: '500',
            }}
          >
            Sign up
          </a>
        </p>
      </div>
    </div>
  );
}

// TODO 2: UserCard
function UserCard() {
  const followUser = () => {
    alert('Following user...');
  };

  const messageUser = () => {
    alert('Opening message...');
  };

  return (
    <div
      style={{
        padding: '20px',
        backgroundColor: 'white',
        borderRadius: '8px',
        boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
        textAlign: 'center',
        maxWidth: '300px',
        margin: '20px',
      }}
    >
      <img
        src='https://i.pravatar.cc/100'
        alt='User avatar'
        style={{
          width: '100px',
          height: '100px',
          borderRadius: '50%',
          objectFit: 'cover',
        }}
      />

      <h3
        style={{
          marginTop: '10px',
          fontSize: '24px',
          color: '#333',
        }}
      >
        John Doe
      </h3>

      <p
        style={{
          color: '#666',
          fontSize: '14px',
        }}
      >
        Software Developer
      </p>

      <div
        style={{
          marginTop: '20px',
          display: 'flex',
          gap: '10px',
          justifyContent: 'center',
        }}
      >
        <button
          onClick={followUser}
          style={{
            padding: '8px 16px',
            backgroundColor: '#3b82f6',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer',
            fontWeight: '500',
          }}
        >
          Follow
        </button>

        <button
          onClick={messageUser}
          style={{
            padding: '8px 16px',
            backgroundColor: 'white',
            color: '#3b82f6',
            border: '1px solid #3b82f6',
            borderRadius: '4px',
            cursor: 'pointer',
            fontWeight: '500',
          }}
        >
          Message
        </button>
      </div>
    </div>
  );
}

// TODO 3: Navbar
function Navbar() {
  const navItems = [
    { label: 'Home', href: '#home', active: true },
    { label: 'About', href: '#about', active: false },
    { label: 'Contact', href: '#contact', active: false },
  ];

  const handleSearch = () => {
    alert('Searching...');
  };

  return (
    <nav
      style={{
        backgroundColor: '#1f2937',
        padding: '16px 32px',
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
        color: 'white',
        flexWrap: 'wrap',
        gap: '16px',
      }}
    >
      <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
        <img
          src='https://via.placeholder.com/40'
          alt='Logo'
          style={{ width: '40px', height: '40px', borderRadius: '4px' }}
        />
        <span style={{ fontSize: '20px', fontWeight: 'bold' }}>MyApp</span>
      </div>

      <ul
        style={{
          display: 'flex',
          listStyle: 'none',
          gap: '24px',
          margin: 0,
          padding: 0,
        }}
      >
        {navItems.map((item, index) => (
          <li key={index}>
            <a
              href={item.href}
              style={{
                color: item.active ? '#60a5fa' : 'white',
                textDecoration: 'none',
                fontWeight: item.active ? 'bold' : 'normal',
                padding: '8px 12px',
                borderRadius: '4px',
                backgroundColor: item.active
                  ? 'rgba(96, 165, 250, 0.1)'
                  : 'transparent',
              }}
            >
              {item.label}
            </a>
          </li>
        ))}
      </ul>

      <div style={{ display: 'flex', gap: '8px' }}>
        <input
          type='search'
          placeholder='Search...'
          style={{
            padding: '8px 12px',
            borderRadius: '4px',
            border: '1px solid #4b5563',
            backgroundColor: '#374151',
            color: 'white',
            outline: 'none',
          }}
        />
        <button
          onClick={handleSearch}
          style={{
            padding: '8px 16px',
            backgroundColor: '#3b82f6',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer',
            fontWeight: '500',
          }}
        >
          Search
        </button>
      </div>
    </nav>
  );
}

// Main App
export default function App() {
  return (
    <div
      style={{
        backgroundColor: '#f3f4f6',
        minHeight: '100vh',
        fontFamily:
          '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
      }}
    >
      <h1
        style={{
          textAlign: 'center',
          padding: '20px',
          color: '#1f2937',
          margin: 0,
        }}
      >
        Exercise 2: HTML to JSX Conversion
      </h1>

      <Navbar />

      <div
        style={{
          display: 'flex',
          gap: '20px',
          flexWrap: 'wrap',
          justifyContent: 'center',
          padding: '40px 20px',
        }}
      >
        <LoginForm />
        <UserCard />
      </div>
    </div>
  );
}
markdown
**🔧 Conversion Checklist:**

- `class``className`
- `for``htmlFor`
- `onsubmit``onSubmit`
- `onclick``onClick`
- `style="string"``style={{ ... }}`
- `background-color``backgroundColor`
- Thẻ self-closing bắt buộc có `/`
- Event handler phải là function, không phải string

🎓 Kiến thức rút ra:

  • Thuộc tính trong JSX dùng camelCase
  • Style viết dưới dạng object JavaScript
  • Event handler truyền vào dưới dạng function
  • Mọi thẻ JSX đều phải được đóng đúng cách
  • Các từ khóa HTML bị trùng phải dùng tên thay thế trong JSX

⭐⭐⭐ Exercise 3: Dynamic UI with JSX (40 phút)

jsx
/**
 * 🎯 Mục tiêu: Xây dựng UI động với JSX expressions
 * ⏱️ Thời gian: 40 phút
 *
 * 📋 Product Requirements:
 * User Story: "Là user, tôi muốn thấy danh sách products với
 * filtering và sorting, để dễ dàng tìm product mình muốn"
 *
 * ✅ Acceptance Criteria:
 * - [ ] Hiển thị list of products
 * - [ ] Show/hide out of stock products
 * - [ ] Filter by category
 * - [ ] Sort by price/name
 * - [ ] Display product count
 * - [ ] Conditional styling based on stock status
 *
 * 🎨 Technical Constraints:
 * - CHỈ dùng: JSX, variables, expressions (Ngày 1-3)
 * - KHÔNG dùng: State, Events with updates, useEffect
 *
 * 🚨 Edge Cases cần handle:
 * - Empty product list
 * - All products out of stock
 * - No products match filter
 */

// Sample data
const products = [
  {
    id: 1,
    name: 'Laptop Pro',
    price: 1299,
    category: 'Electronics',
    inStock: true,
    rating: 4.5,
  },
  {
    id: 2,
    name: 'Wireless Mouse',
    price: 29,
    category: 'Electronics',
    inStock: true,
    rating: 4.2,
  },
  {
    id: 3,
    name: 'Mechanical Keyboard',
    price: 89,
    category: 'Electronics',
    inStock: false,
    rating: 4.7,
  },
  {
    id: 4,
    name: 'USB-C Cable',
    price: 15,
    category: 'Accessories',
    inStock: true,
    rating: 4.0,
  },
  {
    id: 5,
    name: 'Monitor Stand',
    price: 45,
    category: 'Accessories',
    inStock: true,
    rating: 4.3,
  },
  {
    id: 6,
    name: '4K Monitor',
    price: 399,
    category: 'Electronics',
    inStock: false,
    rating: 4.8,
  },
  {
    id: 7,
    name: 'Desk Lamp',
    price: 35,
    category: 'Furniture',
    inStock: true,
    rating: 4.1,
  },
  {
    id: 8,
    name: 'Webcam HD',
    price: 79,
    category: 'Electronics',
    inStock: true,
    rating: 4.4,
  },
];

// TODO 1: Tạo ProductList component
function ProductList() {
  // Config (hardcode for now - sẽ dùng state sau)
  const showOutOfStock = true;
  const selectedCategory = 'All'; // 'All', 'Electronics', 'Accessories', 'Furniture'
  const sortBy = 'name'; // 'name', 'price', 'rating'

  // YOUR CODE HERE
  // Tasks:
  // 1. Filter products based on showOutOfStock và selectedCategory
  // 2. Sort products based on sortBy
  // 3. Display filtered & sorted products
  // 4. Show product count
  // 5. Handle empty results

  return <div>{/* YOUR JSX HERE */}</div>;
}

// TODO 2: Tạo ProductCard component (single product)
function ProductCard({ product }) {
  // YOUR CODE HERE
  // Display:
  // - Product name
  // - Price (formatted)
  // - Category badge
  // - Rating stars
  // - Stock status (with conditional styling)
  // - Different opacity/style if out of stock

  return <div>{/* YOUR JSX HERE */}</div>;
}

// TODO 3: Tạo CategoryBadge component
function CategoryBadge({ category }) {
  // YOUR CODE HERE
  // Different color for each category:
  // - Electronics: blue
  // - Accessories: green
  // - Furniture: orange

  return <span>{/* YOUR JSX HERE */}</span>;
}

// TODO 4: Tạo RatingStars component
function RatingStars({ rating }) {
  // YOUR CODE HERE
  // Display stars based on rating
  // - Full stars for whole numbers
  // - Half star for decimals
  // - Empty stars for remainder
  // Example: 4.5 → ★★★★☆

  return <div>{/* YOUR JSX HERE */}</div>;
}

// TODO 5: Tạo EmptyState component
function EmptyState({ message }) {
  // YOUR CODE HERE
  // Display when no products match filters
  // Show icon + message + suggestion

  return <div>{/* YOUR JSX HERE */}</div>;
}

// Main App
function App() {
  return (
    <div>
      <h1>Product Catalog</h1>
      <ProductList />
    </div>
  );
}

// 📝 Testing Scenarios:
// 1. All products visible
// 2. Hide out of stock → should show 6 products
// 3. Filter by Electronics → should show 5 products
// 4. Filter Electronics + hide out of stock → should show 3 products
// 5. Sort by price ascending
// 6. Sort by rating descending
// 7. No products match filter → show empty state
💡 Solution
jsx
import React from 'react';

/**
 * ✅ SOLUTION: Exercise 3 - Dynamic UI with JSX
 */

// Sample data
const products = [
  {
    id: 1,
    name: 'Laptop Pro',
    price: 1299,
    category: 'Electronics',
    inStock: true,
    rating: 4.5,
  },
  {
    id: 2,
    name: 'Wireless Mouse',
    price: 29,
    category: 'Electronics',
    inStock: true,
    rating: 4.2,
  },
  {
    id: 3,
    name: 'Mechanical Keyboard',
    price: 89,
    category: 'Electronics',
    inStock: false,
    rating: 4.7,
  },
  {
    id: 4,
    name: 'USB-C Cable',
    price: 15,
    category: 'Accessories',
    inStock: true,
    rating: 4.0,
  },
  {
    id: 5,
    name: 'Monitor Stand',
    price: 45,
    category: 'Accessories',
    inStock: true,
    rating: 4.3,
  },
  {
    id: 6,
    name: '4K Monitor',
    price: 399,
    category: 'Electronics',
    inStock: false,
    rating: 4.8,
  },
  {
    id: 7,
    name: 'Desk Lamp',
    price: 35,
    category: 'Furniture',
    inStock: true,
    rating: 4.1,
  },
  {
    id: 8,
    name: 'Webcam HD',
    price: 79,
    category: 'Electronics',
    inStock: true,
    rating: 4.4,
  },
];

// TODO 4: RatingStars component
function RatingStars({ rating }) {
  const fullStars = Math.floor(rating);
  const hasHalfStar = rating % 1 >= 0.5;
  const emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0);

  return (
    <div
      style={{
        color: '#fbbf24',
        fontSize: '16px',
        display: 'flex',
        gap: '2px',
      }}
    >
      {/* Full stars */}
      {Array(fullStars)
        .fill(0)
        .map((_, i) => (
          <span key={`full-${i}`}>★</span>
        ))}

      {/* Half star */}
      {hasHalfStar && <span>⯨</span>}

      {/* Empty stars */}
      {Array(emptyStars)
        .fill(0)
        .map((_, i) => (
          <span
            key={`empty-${i}`}
            style={{ color: '#d1d5db' }}
          >

          </span>
        ))}

      <span style={{ marginLeft: '6px', fontSize: '14px', color: '#6b7280' }}>
        {rating.toFixed(1)}
      </span>
    </div>
  );
}

// TODO 3: CategoryBadge component
function CategoryBadge({ category }) {
  const colors = {
    Electronics: { bg: '#dbeafe', text: '#1e40af' },
    Accessories: { bg: '#d1fae5', text: '#065f46' },
    Furniture: { bg: '#fed7aa', text: '#9a3412' },
  };

  const color = colors[category] || { bg: '#f3f4f6', text: '#374151' };

  return (
    <span
      style={{
        display: 'inline-block',
        padding: '4px 12px',
        backgroundColor: color.bg,
        color: color.text,
        borderRadius: '12px',
        fontSize: '12px',
        fontWeight: '600',
      }}
    >
      {category}
    </span>
  );
}

// TODO 2: ProductCard component
function ProductCard({ product }) {
  return (
    <div
      style={{
        backgroundColor: 'white',
        borderRadius: '8px',
        padding: '20px',
        boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
        opacity: product.inStock ? 1 : 0.6,
        border: product.inStock ? '1px solid #e5e7eb' : '1px solid #fca5a5',
        transition: 'transform 0.2s',
        cursor: 'pointer',
      }}
    >
      <div
        style={{
          display: 'flex',
          justifyContent: 'space-between',
          alignItems: 'flex-start',
          marginBottom: '12px',
        }}
      >
        <h3
          style={{
            margin: 0,
            fontSize: '18px',
            color: product.inStock ? '#1f2937' : '#9ca3af',
          }}
        >
          {product.name}
        </h3>
        <CategoryBadge category={product.category} />
      </div>

      <div style={{ marginBottom: '12px' }}>
        <RatingStars rating={product.rating} />
      </div>

      <div
        style={{
          display: 'flex',
          justifyContent: 'space-between',
          alignItems: 'center',
        }}
      >
        <div
          style={{
            fontSize: '24px',
            fontWeight: 'bold',
            color: product.inStock ? '#059669' : '#9ca3af',
          }}
        >
          ${product.price.toLocaleString()}
        </div>

        <div
          style={{
            padding: '6px 12px',
            backgroundColor: product.inStock ? '#d1fae5' : '#fee2e2',
            color: product.inStock ? '#065f46' : '#991b1b',
            borderRadius: '6px',
            fontSize: '13px',
            fontWeight: '600',
          }}
        >
          {product.inStock ? '✓ In Stock' : '✕ Out of Stock'}
        </div>
      </div>
    </div>
  );
}

// TODO 5: EmptyState component
function EmptyState({ message }) {
  return (
    <div
      style={{
        textAlign: 'center',
        padding: '60px 20px',
        backgroundColor: '#f9fafb',
        borderRadius: '8px',
        border: '2px dashed #d1d5db',
      }}
    >
      <div style={{ fontSize: '64px', marginBottom: '16px' }}>📦</div>
      <h3
        style={{
          fontSize: '20px',
          color: '#6b7280',
          margin: '0 0 8px 0',
        }}
      >
        {message}
      </h3>
      <p style={{ color: '#9ca3af', margin: 0 }}>
        Try adjusting your filters or check back later
      </p>
    </div>
  );
}

// TODO 1: ProductList component
function ProductList() {
  // Configuration (hardcoded for now - will use state later)
  const showOutOfStock = true;
  const selectedCategory = 'All'; // Try: 'Electronics', 'Accessories', 'Furniture'
  const sortBy = 'name'; // Try: 'price', 'rating'

  // Step 1: Filter by stock status
  let filteredProducts = showOutOfStock
    ? products
    : products.filter((p) => p.inStock);

  // Step 2: Filter by category
  if (selectedCategory !== 'All') {
    filteredProducts = filteredProducts.filter(
      (p) => p.category === selectedCategory
    );
  }

  // Step 3: Sort products
  const sortedProducts = [...filteredProducts].sort((a, b) => {
    if (sortBy === 'name') {
      return a.name.localeCompare(b.name);
    } else if (sortBy === 'price') {
      return a.price - b.price;
    } else if (sortBy === 'rating') {
      return b.rating - a.rating; // Descending
    }
    return 0;
  });

  // Calculate stats
  const totalProducts = products.length;
  const displayedProducts = sortedProducts.length;
  const inStockCount = sortedProducts.filter((p) => p.inStock).length;
  const outOfStockCount = sortedProducts.filter((p) => !p.inStock).length;

  return (
    <div style={{ maxWidth: '1200px', margin: '0 auto', padding: '20px' }}>
      {/* Header with stats */}
      <div
        style={{
          backgroundColor: 'white',
          padding: '20px',
          borderRadius: '8px',
          marginBottom: '24px',
          boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
        }}
      >
        <h2 style={{ margin: '0 0 16px 0', color: '#1f2937' }}>
          Product Catalog
        </h2>

        <div
          style={{
            display: 'flex',
            gap: '24px',
            flexWrap: 'wrap',
          }}
        >
          <div>
            <div
              style={{
                fontSize: '12px',
                color: '#6b7280',
                marginBottom: '4px',
              }}
            >
              Total Products
            </div>
            <div
              style={{ fontSize: '24px', fontWeight: 'bold', color: '#1f2937' }}
            >
              {totalProducts}
            </div>
          </div>

          <div>
            <div
              style={{
                fontSize: '12px',
                color: '#6b7280',
                marginBottom: '4px',
              }}
            >
              Showing
            </div>
            <div
              style={{ fontSize: '24px', fontWeight: 'bold', color: '#3b82f6' }}
            >
              {displayedProducts}
            </div>
          </div>

          <div>
            <div
              style={{
                fontSize: '12px',
                color: '#6b7280',
                marginBottom: '4px',
              }}
            >
              In Stock
            </div>
            <div
              style={{ fontSize: '24px', fontWeight: 'bold', color: '#059669' }}
            >
              {inStockCount}
            </div>
          </div>

          <div>
            <div
              style={{
                fontSize: '12px',
                color: '#6b7280',
                marginBottom: '4px',
              }}
            >
              Out of Stock
            </div>
            <div
              style={{ fontSize: '24px', fontWeight: 'bold', color: '#dc2626' }}
            >
              {outOfStockCount}
            </div>
          </div>
        </div>

        {/* Current filters display */}
        <div
          style={{
            marginTop: '16px',
            padding: '12px',
            backgroundColor: '#f9fafb',
            borderRadius: '6px',
            fontSize: '14px',
            color: '#6b7280',
          }}
        >
          <strong>Active Filters:</strong> Category:{' '}
          <span style={{ color: '#3b82f6', fontWeight: '600' }}>
            {selectedCategory}
          </span>{' '}
          | Show Out of Stock:{' '}
          <span style={{ color: '#3b82f6', fontWeight: '600' }}>
            {showOutOfStock ? 'Yes' : 'No'}
          </span>{' '}
          | Sort by:{' '}
          <span style={{ color: '#3b82f6', fontWeight: '600' }}>{sortBy}</span>
        </div>
      </div>

      {/* Product grid */}
      {sortedProducts.length > 0 ? (
        <div
          style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
            gap: '20px',
          }}
        >
          {sortedProducts.map((product) => (
            <ProductCard
              key={product.id}
              product={product}
            />
          ))}
        </div>
      ) : (
        <EmptyState message='No products found' />
      )}
    </div>
  );
}

// Main App
export default function App() {
  return (
    <div
      style={{
        backgroundColor: '#f3f4f6',
        minHeight: '100vh',
        fontFamily:
          '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
      }}
    >
      <ProductList />
    </div>
  );
}
**🎓 Những kiến thức đã học:**

- **Lọc mảng (Filtering arrays):** Sử dụng `.filter()` để hiển thị / ẩn sản phẩm theo điều kiện
- **Sắp xếp mảng (Sorting arrays):** Dùng `.sort()` với nhiều tiêu chí khác nhau
- **Render có điều kiện:** Thay đổi UI dựa trên dữ liệu
- **Style có điều kiện:** Màu sắc / độ mờ thay đổi theo trạng thái
- **Component composition:** Chia UI thành các component nhỏ, tái sử dụng
- **Render danh sách:** Dùng `.map()` để hiển thị list
- **Tính toán trước khi render:** Xử lý logic trước, JSX chỉ để hiển thị

**💡 Thử thay đổi để hiểu sâu hơn:**

- Đặt `showOutOfStock = false` để ẩn sản phẩm hết hàng
- Thay đổi `selectedCategory` thành `Electronics` hoặc `Accessories`
- Đổi `sortBy` sang `price` hoặc `rating`
- Chỉnh lại màu sắc trong component `CategoryBadge`

⭐⭐⭐⭐ Exercise 4: Component Composition Architecture (60 phút)

jsx
/**
 * 🎯 Mục tiêu: Thiết kế component architecture hợp lý
 * ⏱️ Thời gian: 60 phút
 *
 * 🏗️ PHASE 1: Research & Design (20 phút)
 *
 * Scenario: Bạn cần build Dashboard Layout với:
 * - Header (logo, navigation, user menu)
 * - Sidebar (navigation links)
 * - Main content area
 * - Footer
 *
 * 📋 ADR (Architecture Decision Record):
 *
 * ## Context
 * Cần component structure dễ maintain và reusable
 *
 * ## Decision Points
 *
 * 1. Nên chia components như thế nào?
 *    - Option A: Tất cả trong 1 component
 *    - Option B: Chia theo UI sections (Header, Sidebar, Main, Footer)
 *    - Option C: Chia theo functionality (Navigation, Content, UserInfo)
 *
 * 2. Nên đặt tên components thế nào?
 *    - Option A: Generic (Box, Container, Section)
 *    - Option B: Specific (DashboardHeader, DashboardSidebar)
 *    - Option C: Mix (Layout.Header, Layout.Sidebar)
 *
 * 3. Component nesting depth?
 *    - Option A: Flat structure (ít nesting)
 *    - Option B: Deep nesting (hierarchical)
 *    - Option C: Balanced (2-3 levels)
 */

// YOUR ANALYSIS HERE
// Document your decisions với rationale

// ========================================
// 💻 PHASE 2: Implementation (30 phút)
// ========================================

/**
 * TODO: Implement Dashboard Layout
 * Requirements:
 * - Reusable components
 * - Clear component hierarchy
 * - Semantic naming
 * - Props for customization (CHỈ pass data, KHÔNG có callbacks)
 */

// Component Structure Example:
// App
//   ├─ DashboardLayout
//   │   ├─ Header
//   │   │   ├─ Logo
//   │   │   ├─ Navigation
//   │   │   └─ UserMenu
//   │   ├─ Sidebar
//   │   │   └─ NavLinks
//   │   ├─ MainContent
//   │   └─ Footer

// TODO 1: Tạo Logo component
function Logo({ appName, size }) {
  // YOUR CODE HERE
  // Display logo với app name
  // Size variants: 'small', 'medium', 'large'
}

// TODO 2: Tạo Navigation component
function Navigation({ links }) {
  // YOUR CODE HERE
  // links = [{ label: 'Home', href: '/', active: true }, ...]
  // Hiển thị nav links với active state
}

// TODO 3: Tạo UserMenu component
function UserMenu({ user }) {
  // YOUR CODE HERE
  // user = { name: 'John', avatar: 'url', role: 'Admin' }
  // Display user info
}

// TODO 4: Tạo Header component (compose Logo + Navigation + UserMenu)
function Header({ appName, navLinks, user }) {
  // YOUR CODE HERE
  // Compose các components nhỏ hơn
}

// TODO 5: Tạo Sidebar component
function Sidebar({ links }) {
  // YOUR CODE HERE
  // Vertical navigation
  // Group links by category nếu có
}

// TODO 6: Tạo MainContent component
function MainContent({ title, children }) {
  // YOUR CODE HERE
  // Wrapper cho main content area
  // children = actual page content
}

// TODO 7: Tạo Footer component
function Footer({ companyName, year, links }) {
  // YOUR CODE HERE
  // Copyright info + footer links
}

// TODO 8: Tạo DashboardLayout component (compose all)
function DashboardLayout({
  appName,
  headerLinks,
  sidebarLinks,
  user,
  children,
}) {
  // YOUR CODE HERE
  // Compose: Header + Sidebar + MainContent + Footer
  // Use CSS Grid or Flexbox for layout
}

// ========================================
// 🧪 PHASE 3: Testing (10 phút)
// ========================================

function App() {
  // Sample data
  const appName = 'My Dashboard';

  const headerLinks = [
    { label: 'Dashboard', href: '/', active: true },
    { label: 'Analytics', href: '/analytics', active: false },
    { label: 'Settings', href: '/settings', active: false },
  ];

  const sidebarLinks = [
    {
      category: 'Main',
      items: [
        { label: 'Home', href: '/', icon: '🏠', active: true },
        { label: 'Projects', href: '/projects', icon: '📁', active: false },
      ],
    },
    {
      category: 'Tools',
      items: [
        { label: 'Calendar', href: '/calendar', icon: '📅', active: false },
        { label: 'Tasks', href: '/tasks', icon: '✓', active: false },
      ],
    },
  ];

  const user = {
    name: 'John Doe',
    email: 'john@example.com',
    avatar: 'https://i.pravatar.cc/40',
    role: 'Admin',
  };

  return (
    <DashboardLayout
      appName={appName}
      headerLinks={headerLinks}
      sidebarLinks={sidebarLinks}
      user={user}
    >
      {/* Main page content */}
      <div>
        <h1>Welcome to Dashboard</h1>
        <p>This is the main content area.</p>
      </div>
    </DashboardLayout>
  );
}

// 📝 Evaluation Criteria:
// - [ ] Clear component hierarchy
// - [ ] Reusable components
// - [ ] Semantic naming
// - [ ] Props properly structured
// - [ ] No hardcoded values
// - [ ] Layout works responsively
// - [ ] Code is readable
💡 Solution
jsx
// Exercise 4: Component Composition Architecture
// Dashboard Layout với cấu trúc component rõ ràng, dễ bảo trì và tái sử dụng

import React from 'react';

// =============================================
// TODO 1: Logo component
// =============================================
function Logo({ appName, size = 'medium' }) {
  // size variants: small, medium, large
  const sizes = {
    small: { fontSize: '1.4rem', gap: '6px' },
    medium: { fontSize: '1.8rem', gap: '8px' },
    large: { fontSize: '2.2rem', gap: '10px' },
  };

  const style = sizes[size] || sizes.medium;

  return (
    <div
      className='logo'
      style={{ display: 'flex', alignItems: 'center', gap: style.gap }}
    >
      <div
        style={{
          width: size === 'large' ? 48 : size === 'small' ? 32 : 40,
          height: size === 'large' ? 48 : size === 'small' ? 32 : 40,
          background: 'linear-gradient(135deg, #667eea, #764ba2)',
          borderRadius: '12px',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          color: 'white',
          fontWeight: 'bold',
          fontSize:
            size === 'large'
              ? '1.6rem'
              : size === 'small'
              ? '1.1rem'
              : '1.4rem',
        }}
      >
        {appName.charAt(0)}
      </div>
      <span
        style={{ fontSize: style.fontSize, fontWeight: 700, color: '#1f2937' }}
      >
        {appName}
      </span>
    </div>
  );
}

// =============================================
// TODO 2: Navigation component (dùng cho header)
// =============================================
function Navigation({ links }) {
  return (
    <nav className='main-navigation'>
      <ul
        style={{
          display: 'flex',
          listStyle: 'none',
          gap: '32px',
          margin: 0,
          padding: 0,
        }}
      >
        {links.map((link) => (
          <li key={link.href}>
            <a
              href={link.href}
              style={{
                color: link.active ? '#3b82f6' : '#4b5563',
                textDecoration: 'none',
                fontWeight: link.active ? 600 : 500,
                padding: '8px 12px',
                borderRadius: '6px',
                background: link.active
                  ? 'rgba(59, 130, 246, 0.1)'
                  : 'transparent',
              }}
            >
              {link.label}
            </a>
          </li>
        ))}
      </ul>
    </nav>
  );
}

// =============================================
// TODO 3: UserMenu component
// =============================================
function UserMenu({ user }) {
  return (
    <div
      className='user-menu'
      style={{ display: 'flex', alignItems: 'center', gap: '12px' }}
    >
      <img
        src={user.avatar}
        alt={`${user.name} avatar`}
        style={{
          width: 40,
          height: 40,
          borderRadius: '50%',
          objectFit: 'cover',
          border: '2px solid #e5e7eb',
        }}
      />
      <div>
        <div style={{ fontWeight: 600, color: '#1f2937' }}>{user.name}</div>
        <div style={{ fontSize: '0.85rem', color: '#6b7280' }}>{user.role}</div>
      </div>
    </div>
  );
}

// =============================================
// TODO 4: Header component (compose Logo + Navigation + UserMenu)
// =============================================
function Header({ appName, navLinks, user }) {
  return (
    <header
      style={{
        background: 'white',
        borderBottom: '1px solid #e5e7eb',
        padding: '16px 32px',
        position: 'sticky',
        top: 0,
        zIndex: 100,
        boxShadow: '0 1px 3px rgba(0,0,0,0.05)',
      }}
    >
      <div
        style={{
          display: 'flex',
          justifyContent: 'space-between',
          alignItems: 'center',
          maxWidth: '1400px',
          margin: '0 auto',
        }}
      >
        <Logo
          appName={appName}
          size='medium'
        />

        <Navigation links={navLinks} />

        <UserMenu user={user} />
      </div>
    </header>
  );
}

// =============================================
// TODO 5: Sidebar component (có nhóm category)
// =============================================
function Sidebar({ links }) {
  return (
    <aside
      style={{
        width: 260,
        background: '#f8f9fa',
        borderRight: '1px solid #e5e7eb',
        padding: '24px 16px',
        height: 'calc(100vh - 64px)', // trừ header height
        position: 'fixed',
        overflowY: 'auto',
      }}
    >
      {links.map((group) => (
        <div
          key={group.category}
          style={{ marginBottom: '24px' }}
        >
          <h4
            style={{
              color: '#6b7280',
              fontSize: '0.85rem',
              fontWeight: 600,
              margin: '0 0 12px 12px',
              textTransform: 'uppercase',
              letterSpacing: '0.5px',
            }}
          >
            {group.category}
          </h4>

          <ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>
            {group.items.map((item) => (
              <li key={item.href}>
                <a
                  href={item.href}
                  style={{
                    display: 'flex',
                    alignItems: 'center',
                    gap: '12px',
                    padding: '12px 16px',
                    color: item.active ? '#3b82f6' : '#4b5563',
                    textDecoration: 'none',
                    borderRadius: '8px',
                    background: item.active
                      ? 'rgba(59, 130, 246, 0.1)'
                      : 'transparent',
                    fontWeight: item.active ? 600 : 500,
                    transition: 'all 0.15s',
                  }}
                >
                  <span style={{ fontSize: '1.2rem' }}>{item.icon}</span>
                  {item.label}
                </a>
              </li>
            ))}
          </ul>
        </div>
      ))}
    </aside>
  );
}

// =============================================
// TODO 6: MainContent component
// =============================================
function MainContent({ title, children }) {
  return (
    <main
      style={{
        marginLeft: 260, // bằng width của sidebar
        padding: '32px 40px',
        minHeight: 'calc(100vh - 64px)',
      }}
    >
      {title && (
        <h1
          style={{
            margin: '0 0 32px',
            fontSize: '2rem',
            color: '#1f2937',
          }}
        >
          {title}
        </h1>
      )}
      {children}
    </main>
  );
}

// =============================================
// TODO 7: Footer component
// =============================================
function Footer({ companyName, year, links = [] }) {
  return (
    <footer
      style={{
        background: '#1f2937',
        color: 'white',
        padding: '32px 0',
        marginTop: 'auto',
      }}
    >
      <div
        style={{
          maxWidth: '1400px',
          margin: '0 auto',
          padding: '0 40px',
          display: 'flex',
          justifyContent: 'space-between',
          alignItems: 'center',
          flexWrap: 'wrap',
          gap: '20px',
        }}
      >
        <div>
          <strong>{companyName}</strong> © {year}
        </div>

        <ul
          style={{
            display: 'flex',
            gap: '24px',
            listStyle: 'none',
            margin: 0,
            padding: 0,
          }}
        >
          {links.map((link) => (
            <li key={link.href}>
              <a
                href={link.href}
                style={{ color: '#d1d5db', textDecoration: 'none' }}
              >
                {link.label}
              </a>
            </li>
          ))}
        </ul>
      </div>
    </footer>
  );
}

// =============================================
// TODO 8: DashboardLayout - component gốc ghép tất cả
// =============================================
function DashboardLayout({
  appName,
  headerLinks,
  sidebarLinks,
  user,
  children,
  title,
}) {
  return (
    <div
      style={{
        display: 'flex',
        flexDirection: 'column',
        minHeight: '100vh',
        background: '#f3f4f6',
      }}
    >
      <Header
        appName={appName}
        navLinks={headerLinks}
        user={user}
      />

      <div style={{ display: 'flex', flex: 1 }}>
        <Sidebar links={sidebarLinks} />
        <MainContent title={title}>{children}</MainContent>
      </div>

      <Footer
        companyName={appName}
        year={new Date().getFullYear()}
        links={[
          { label: 'Về chúng tôi', href: '/about' },
          { label: 'Hỗ trợ', href: '/support' },
          { label: 'Điều khoản', href: '/terms' },
        ]}
      />
    </div>
  );
}

// =============================================
// App - Testing & Demo
// =============================================
function App() {
  const appName = 'My Dashboard';

  const headerLinks = [
    { label: 'Dashboard', href: '/', active: true },
    { label: 'Analytics', href: '/analytics', active: false },
    { label: 'Settings', href: '/settings', active: false },
  ];

  const sidebarLinks = [
    {
      category: 'Main',
      items: [
        { label: 'Home', href: '/', icon: '🏠', active: true },
        { label: 'Projects', href: '/projects', icon: '📁', active: false },
      ],
    },
    {
      category: 'Tools',
      items: [
        { label: 'Calendar', href: '/calendar', icon: '📅', active: false },
        { label: 'Tasks', href: '/tasks', icon: '✓', active: false },
      ],
    },
  ];

  const user = {
    name: 'John Doe',
    email: 'john@example.com',
    avatar: 'https://i.pravatar.cc/40',
    role: 'Admin',
  };

  return (
    <DashboardLayout
      appName={appName}
      headerLinks={headerLinks}
      sidebarLinks={sidebarLinks}
      user={user}
      title='Trang tổng quan'
    >
      <div
        style={{
          background: 'white',
          padding: '32px',
          borderRadius: '12px',
          boxShadow: '0 4px 12px rgba(0,0,0,0.05)',
        }}
      >
        <h1>Chào mừng quay lại, {user.name}!</h1>
        <p>Đây là khu vực nội dung chính của dashboard.</p>
        <p>
          Bạn có thể đặt bất kỳ component nội dung nào vào đây thông qua
          children prop.
        </p>
      </div>
    </DashboardLayout>
  );
}

export default App;

Ghi chú thiết kế (ADR – Architecture Decision Record):

  1. Cách chia component → Chọn Option B + C kết hợp
    → Chia theo UI sections (Header, Sidebar, Main, Footer)
    → Nhưng bên trong Header còn chia nhỏ hơn: Logo + Navigation + UserMenu
    → Lý do: Tăng tính tái sử dụng và dễ test, đồng thời giữ cấu trúc rõ ràng

  2. Cách đặt tên → Chọn Option B (Specific)
    → DashboardHeader, DashboardSidebar, DashboardFooter…
    → Nhưng trong code dùng tên ngắn gọn: Header, Sidebar, Footer
    → Lý do: Trong ngữ cảnh chỉ có 1 dashboard → không cần tiền tố dài dòng

  3. Nesting depth → Chọn Option C: Balanced (2-3 levels)
    → App → DashboardLayout → (Header, Sidebar, MainContent, Footer)
    → Header → (Logo, Navigation, UserMenu)
    → Không quá sâu, vẫn dễ theo dõi

  4. Props → Chỉ truyền data xuống, không truyền callback (theo yêu cầu)
    → Điều này giúp các component con thuần túy (presentational), dễ test

  5. Layout → Sử dụng position: fixed cho Sidebar + margin-left cho Main
    → Responsive: Sidebar cố định bên trái, nội dung scroll độc lập

Nếu muốn nâng cao thêm, có thể:

  • Thêm responsive: ẩn Sidebar trên mobile → nút hamburger
  • Thêm dark mode toggle trong UserMenu
  • Làm collapsible Sidebar

⭐⭐⭐⭐⭐ Exercise 5: Production-Ready Component Library (90 phút)

jsx
/**
 * 🎯 Mục tiêu: Tạo mini component library production-ready
 * ⏱️ Thời gian: 90 phút
 *
 * 📋 Feature Specification:
 *
 * Xây dựng "UI Component Library" với:
 * 1. Button component (variants, sizes, states)
 * 2. Card component (với header, body, footer)
 * 3. Badge component (status indicators)
 * 4. Alert component (success, warning, error, info)
 * 5. Avatar component (with fallback)
 *
 * 🏗️ Technical Design Doc:
 *
 * 1. Component Architecture:
 *    - Pure presentational components
 *    - Props for all customization
 *    - Consistent API design
 *    - Accessibility considerations
 *
 * 2. Styling Strategy:
 *    - Inline styles với JavaScript objects
 *    - Consistent design tokens (colors, spacing, fonts)
 *    - Responsive considerations
 *
 * 3. Component API Design:
 *    - Intuitive prop names
 *    - Sensible defaults
 *    - Flexibility without complexity
 *
 * ✅ Production Checklist:
 * - [ ] All components documented
 * - [ ] Props have defaults
 * - [ ] Handle edge cases (empty data, long text)
 * - [ ] Consistent styling
 * - [ ] Accessibility (semantic HTML, aria labels)
 * - [ ] Examples for each component
 * - [ ] Responsive design
 */

// ========================================
// DESIGN TOKENS
// ========================================

const DESIGN_TOKENS = {
  colors: {
    primary: '#3b82f6',
    secondary: '#6b7280',
    success: '#10b981',
    warning: '#f59e0b',
    error: '#ef4444',
    info: '#3b82f6',
    white: '#ffffff',
    black: '#000000',
    gray: {
      50: '#f9fafb',
      100: '#f3f4f6',
      200: '#e5e7eb',
      300: '#d1d5db',
      500: '#6b7280',
      700: '#374151',
      900: '#111827',
    },
  },
  spacing: {
    xs: '4px',
    sm: '8px',
    md: '16px',
    lg: '24px',
    xl: '32px',
  },
  fontSize: {
    xs: '12px',
    sm: '14px',
    base: '16px',
    lg: '18px',
    xl: '20px',
    '2xl': '24px',
  },
  borderRadius: {
    sm: '4px',
    md: '8px',
    lg: '12px',
    full: '9999px',
  },
  shadows: {
    sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
    md: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
    lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
  },
};

// ========================================
// TODO 1: Button Component
// ========================================

/**
 * Button component với multiple variants
 * @param {Object} props
 * @param {string} props.variant - 'primary' | 'secondary' | 'outline' | 'ghost'
 * @param {string} props.size - 'sm' | 'md' | 'lg'
 * @param {boolean} props.disabled - Disabled state
 * @param {ReactNode} props.children - Button content
 */
function Button({
  variant = 'primary',
  size = 'md',
  disabled = false,
  children,
}) {
  // YOUR CODE HERE
  // Implement button styles based on variant and size
  // Handle disabled state
  // Use DESIGN_TOKENS for consistency
}

// ========================================
// TODO 2: Card Component
// ========================================

/**
 * Card container component
 * @param {Object} props
 * @param {ReactNode} props.header - Card header content
 * @param {ReactNode} props.children - Card body content
 * @param {ReactNode} props.footer - Card footer content
 * @param {boolean} props.hoverable - Add hover effect
 */
function Card({ header, children, footer, hoverable = false }) {
  // YOUR CODE HERE
  // Conditional sections (header, footer)
  // Hover effects if hoverable
}

// ========================================
// TODO 3: Badge Component
// ========================================

/**
 * Badge for status/labels
 * @param {Object} props
 * @param {string} props.variant - 'success' | 'warning' | 'error' | 'info'
 * @param {string} props.size - 'sm' | 'md'
 * @param {ReactNode} props.children - Badge content
 */
function Badge({ variant = 'info', size = 'md', children }) {
  // YOUR CODE HERE
  // Different colors for different variants
  // Size variations
}

// ========================================
// TODO 4: Alert Component
// ========================================

/**
 * Alert notification component
 * @param {Object} props
 * @param {string} props.variant - 'success' | 'warning' | 'error' | 'info'
 * @param {string} props.title - Alert title
 * @param {ReactNode} props.children - Alert message
 * @param {boolean} props.dismissible - Show close button
 */
function Alert({ variant = 'info', title, children, dismissible = false }) {
  // YOUR CODE HERE
  // Icon based on variant
  // Optional title
  // Dismissible functionality (just show button for now)
}

// ========================================
// TODO 5: Avatar Component
// ========================================

/**
 * Avatar component with fallback
 * @param {Object} props
 * @param {string} props.src - Image URL
 * @param {string} props.alt - Alt text
 * @param {string} props.fallback - Fallback text (initials)
 * @param {string} props.size - 'sm' | 'md' | 'lg'
 */
function Avatar({ src, alt, fallback, size = 'md' }) {
  // YOUR CODE HERE
  // Show image if src exists
  // Show fallback initials if no src
  // Different sizes
}

// ========================================
// TODO 6: Showcase All Components
// ========================================

function ComponentShowcase() {
  return (
    <div
      style={{
        padding: DESIGN_TOKENS.spacing.xl,
        backgroundColor: DESIGN_TOKENS.colors.gray[50],
        minHeight: '100vh',
      }}
    >
      <h1>UI Component Library</h1>

      {/* Button Section */}
      <section style={{ marginBottom: DESIGN_TOKENS.spacing.xl }}>
        <h2>Buttons</h2>
        <div
          style={{
            display: 'flex',
            gap: DESIGN_TOKENS.spacing.md,
            flexWrap: 'wrap',
          }}
        >
          <Button variant='primary'>Primary</Button>
          <Button variant='secondary'>Secondary</Button>
          <Button variant='outline'>Outline</Button>
          <Button variant='ghost'>Ghost</Button>
          <Button disabled>Disabled</Button>
        </div>

        <h3>Sizes</h3>
        <div
          style={{
            display: 'flex',
            gap: DESIGN_TOKENS.spacing.md,
            alignItems: 'center',
          }}
        >
          <Button size='sm'>Small</Button>
          <Button size='md'>Medium</Button>
          <Button size='lg'>Large</Button>
        </div>
      </section>

      {/* Card Section */}
      <section style={{ marginBottom: DESIGN_TOKENS.spacing.xl }}>
        <h2>Cards</h2>
        <div
          style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
            gap: DESIGN_TOKENS.spacing.md,
          }}
        >
          <Card
            header={<h3>Card Title</h3>}
            footer={<Button variant='primary'>Action</Button>}
          >
            <p>
              This is the card content. Cards are great for grouping related
              information.
            </p>
          </Card>

          <Card hoverable>
            <h3>Hoverable Card</h3>
            <p>Hover over this card to see the effect.</p>
          </Card>
        </div>
      </section>

      {/* Badge Section */}
      <section style={{ marginBottom: DESIGN_TOKENS.spacing.xl }}>
        <h2>Badges</h2>
        <div
          style={{
            display: 'flex',
            gap: DESIGN_TOKENS.spacing.md,
            flexWrap: 'wrap',
            alignItems: 'center',
          }}
        >
          <Badge variant='success'>Success</Badge>
          <Badge variant='warning'>Warning</Badge>
          <Badge variant='error'>Error</Badge>
          <Badge variant='info'>Info</Badge>
          <Badge size='sm'>Small Badge</Badge>
        </div>
      </section>

      {/* Alert Section */}
      <section style={{ marginBottom: DESIGN_TOKENS.spacing.xl }}>
        <h2>Alerts</h2>
        <div
          style={{
            display: 'flex',
            flexDirection: 'column',
            gap: DESIGN_TOKENS.spacing.md,
          }}
        >
          <Alert
            variant='success'
            title='Success!'
          >
            Your changes have been saved successfully.
          </Alert>
          <Alert
            variant='warning'
            title='Warning'
          >
            Please review your information before submitting.
          </Alert>
          <Alert
            variant='error'
            title='Error'
            dismissible
          >
            Something went wrong. Please try again.
          </Alert>
          <Alert variant='info'>This is an informational message.</Alert>
        </div>
      </section>

      {/* Avatar Section */}
      <section style={{ marginBottom: DESIGN_TOKENS.spacing.xl }}>
        <h2>Avatars</h2>
        <div
          style={{
            display: 'flex',
            gap: DESIGN_TOKENS.spacing.md,
            alignItems: 'center',
          }}
        >
          <Avatar
            src='https://i.pravatar.cc/100'
            alt='User'
            size='sm'
          />
          <Avatar
            src='https://i.pravatar.cc/100'
            alt='User'
            size='md'
          />
          <Avatar
            src='https://i.pravatar.cc/100'
            alt='User'
            size='lg'
          />
          <Avatar
            fallback='JD'
            size='md'
          />
          <Avatar
            fallback='AB'
            size='lg'
          />
        </div>
      </section>
    </div>
  );
}

// ========================================
// 📝 DOCUMENTATION
// ========================================

/**
 * Component Library Documentation
 *
 * ## Usage Examples
 *
 * ### Button
 * ```jsx
 * <Button variant="primary" size="md">Click me</Button>
 * <Button variant="outline" disabled>Disabled</Button>
 * ```
 *
 * ### Card
 * ```jsx
 * <Card
 *   header={<h3>Title</h3>}
 *   footer={<Button>Action</Button>}
 * >
 *   Content here
 * </Card>
 * ```
 *
 * ### Badge
 * ```jsx
 * <Badge variant="success">Active</Badge>
 * <Badge variant="error" size="sm">Error</Badge>
 * ```
 *
 * ### Alert
 * ```jsx
 * <Alert variant="warning" title="Warning" dismissible>
 *   Message here
 * </Alert>
 * ```
 *
 * ### Avatar
 * ```jsx
 * <Avatar src="url" alt="User" size="md" />
 * <Avatar fallback="JD" size="lg" />
 * ```
 */

// 🔍 CODE REVIEW SELF-CHECKLIST:
// - [ ] All components implemented
// - [ ] Props have sensible defaults
// - [ ] Using DESIGN_TOKENS consistently
// - [ ] Components are reusable
// - [ ] Handling edge cases
// - [ ] Clean, readable code
// - [ ] Semantic HTML
// - [ ] Accessibility considerations
// - [ ] Documentation complete
// - [ ] Examples work correctly
💡 Solution
jsx
// Exercise 5: Production-Ready Component Library (Mini UI Kit)
// Hoàn thiện các component còn thiếu: Card, Badge, Alert, Avatar
// Sử dụng DESIGN_TOKENS nhất quán, xử lý edge cases, thêm accessibility cơ bản

// ========================================
// TODO 1: Button Component
// ========================================
/**
 * Button component với multiple variants
 * @param {Object} props
 * @param {string} props.variant - 'primary' | 'secondary' | 'outline' | 'ghost'
 * @param {string} props.size - 'sm' | 'md' | 'lg'
 * @param {boolean} props.disabled - Disabled state
 * @param {ReactNode} props.children - Button content
 */
function Button({
  variant = 'primary',
  size = 'md',
  disabled = false,
  children,
  ...rest
}) {
  // Style mapping cho từng variant
  const variantStyles = {
    primary: {
      bg: DESIGN_TOKENS.colors.primary,
      text: DESIGN_TOKENS.colors.white,
      border: 'none',
      hover: '#2563eb',
    },
    secondary: {
      bg: DESIGN_TOKENS.colors.secondary,
      text: DESIGN_TOKENS.colors.white,
      border: 'none',
      hover: '#4b5563',
    },
    outline: {
      bg: 'transparent',
      text: DESIGN_TOKENS.colors.primary,
      border: `1px solid ${DESIGN_TOKENS.colors.primary}`,
      hover: 'rgba(59, 130, 246, 0.1)',
    },
    ghost: {
      bg: 'transparent',
      text: DESIGN_TOKENS.colors.gray[900],
      border: 'none',
      hover: DESIGN_TOKENS.colors.gray[100],
    },
  };

  // Style mapping cho size
  const sizeStyles = {
    sm: {
      padding: `${DESIGN_TOKENS.spacing.xs} ${DESIGN_TOKENS.spacing.sm}`,
      fontSize: DESIGN_TOKENS.fontSize.sm,
    },
    md: {
      padding: `${DESIGN_TOKENS.spacing.sm} ${DESIGN_TOKENS.spacing.md}`,
      fontSize: DESIGN_TOKENS.fontSize.base,
    },
    lg: {
      padding: `${DESIGN_TOKENS.spacing.md} ${DESIGN_TOKENS.spacing.lg}`,
      fontSize: DESIGN_TOKENS.fontSize.lg,
    },
  };

  const style = variantStyles[variant] || variantStyles.primary;
  const sizeStyle = sizeStyles[size] || sizeStyles.md;

  const buttonStyle = {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    padding: sizeStyle.padding,
    fontSize: sizeStyle.fontSize,
    fontWeight: 500,
    color: style.text,
    backgroundColor: disabled ? DESIGN_TOKENS.colors.gray[300] : style.bg,
    border: style.border || 'none',
    borderRadius: DESIGN_TOKENS.borderRadius.md,
    cursor: disabled ? 'not-allowed' : 'pointer',
    opacity: disabled ? 0.6 : 1,
    transition: 'all 0.2s ease',
    boxShadow: DESIGN_TOKENS.shadows.sm,
    minWidth: '100px',
  };

  return (
    <button
      style={buttonStyle}
      disabled={disabled}
      onMouseEnter={(e) => {
        if (!disabled) {
          e.target.style.backgroundColor = style.hover || style.bg;
        }
      }}
      onMouseLeave={(e) => {
        if (!disabled) {
          e.target.style.backgroundColor = style.bg;
        }
      }}
      {...rest}
    >
      {children}
    </button>
  );
}

// ========================================
// TODO 2: Card Component
// ========================================
// NOTE:
// - Component này là presentational (pure UI)
// - Inline style KHÔNG hỗ trợ :hover
// - Tuy nhiên có thể mô phỏng hover bằng:
//   onMouseEnter / onMouseLeave + chỉnh style trực tiếp
// - KHÔNG dùng state trong ví dụ này

/**
 * Card Component
 * @component
 *
 * @param {Object} props - Props truyền vào Card
 * @param {React.ReactNode} [props.header] - Nội dung header của card
 * @param {React.ReactNode} props.children - Nội dung chính của card
 * @param {React.ReactNode} [props.footer] - Nội dung footer của card
 * @param {boolean} [props.hoverable=false] - Bật/tắt hiệu ứng hover
 *
 * @example
 * <Card
 *   header={<h3>User Info</h3>}
 *   footer={<Button>Save</Button>}
 *   hoverable
 * >
 *   <p>Name: John Doe</p>
 * </Card>
 */

function Card({ header, children, footer, hoverable = false }) {
  const baseStyle = {
    backgroundColor: DESIGN_TOKENS.colors.white,
    borderRadius: DESIGN_TOKENS.borderRadius.lg,
    boxShadow: DESIGN_TOKENS.shadows.md,
    overflow: 'hidden',
    transition: hoverable ? 'all 0.2s ease' : 'none',
    cursor: hoverable ? 'pointer' : 'default',
  };

  const handleMouseEnter = (e) => {
    if (!hoverable) return;

    e.currentTarget.style.boxShadow = DESIGN_TOKENS.shadows.lg;
    e.currentTarget.style.transform = 'translateY(-2px)';
  };

  const handleMouseLeave = (e) => {
    if (!hoverable) return;

    e.currentTarget.style.boxShadow = DESIGN_TOKENS.shadows.md;
    e.currentTarget.style.transform = 'none';
  };

  return (
    <div
      style={baseStyle}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
      {header && (
        <div
          style={{
            padding: DESIGN_TOKENS.spacing.lg,
            borderBottom: `1px solid ${DESIGN_TOKENS.colors.gray[200]}`,
            backgroundColor: DESIGN_TOKENS.colors.gray[50],
          }}
        >
          {header}
        </div>
      )}

      <div style={{ padding: DESIGN_TOKENS.spacing.lg }}>{children}</div>

      {footer && (
        <div
          style={{
            padding: `${DESIGN_TOKENS.spacing.md} ${DESIGN_TOKENS.spacing.lg}`,
            borderTop: `1px solid ${DESIGN_TOKENS.colors.gray[200]}`,
            backgroundColor: DESIGN_TOKENS.colors.gray[50],
          }}
        >
          {footer}
        </div>
      )}
    </div>
  );
}

// ========================================
// TODO 3: Badge Component
// ========================================
function Badge({ variant = 'info', size = 'md', children }) {
  const variants = {
    success: { bg: '#d1fae5', text: '#065f46', border: '#059669' },
    warning: { bg: '#fef3c7', text: '#92400e', border: '#d97706' },
    error: { bg: '#fee2e2', text: '#991b1b', border: '#dc2626' },
    info: { bg: '#dbeafe', text: '#1e40af', border: '#3b82f6' },
  };

  const sizes = {
    sm: { padding: '2px 8px', fontSize: DESIGN_TOKENS.fontSize.xs },
    md: { padding: '4px 12px', fontSize: DESIGN_TOKENS.fontSize.sm },
  };

  const style = variants[variant] || variants.info;
  const sizeStyle = sizes[size] || sizes.md;

  return (
    <span
      style={{
        display: 'inline-flex',
        alignItems: 'center',
        justifyContent: 'center',
        padding: sizeStyle.padding,
        fontSize: sizeStyle.fontSize,
        fontWeight: 500,
        backgroundColor: style.bg,
        color: style.text,
        border: `1px solid ${style.border}`,
        borderRadius: DESIGN_TOKENS.borderRadius.full,
      }}
    >
      {children}
    </span>
  );
}

// ========================================
// TODO 4: Alert Component
// ========================================
function Alert({ variant = 'info', title, children, dismissible = false }) {
  const variants = {
    success: {
      bg: '#ecfdf5',
      border: '#059669',
      text: '#065f46',
      icon: '✅',
    },
    warning: {
      bg: '#fffbeb',
      border: '#d97706',
      text: '#92400e',
      icon: '⚠️',
    },
    error: {
      bg: '#fef2f2',
      border: '#dc2626',
      text: '#991b1b',
      icon: '❌',
    },
    info: {
      bg: '#eff6ff',
      border: '#2563eb',
      text: '#1e40af',
      icon: 'ℹ️',
    },
  };

  const style = variants[variant] || variants.info;

  return (
    <div
      role='alert'
      style={{
        display: 'flex',
        alignItems: 'flex-start',
        gap: DESIGN_TOKENS.spacing.md,
        padding: DESIGN_TOKENS.spacing.lg,
        backgroundColor: style.bg,
        borderLeft: `4px solid ${style.border}`,
        borderRadius: DESIGN_TOKENS.borderRadius.md,
        color: style.text,
      }}
    >
      <span style={{ fontSize: '1.5rem', lineHeight: 1 }}>{style.icon}</span>

      <div style={{ flex: 1 }}>
        {title && (
          <h4
            style={{
              margin: '0 0 8px 0',
              fontSize: DESIGN_TOKENS.fontSize.lg,
              fontWeight: 600,
            }}
          >
            {title}
          </h4>
        )}
        <div>{children}</div>
      </div>

      {dismissible && (
        <button
          aria-label='Đóng thông báo'
          style={{
            background: 'none',
            border: 'none',
            fontSize: '1.2rem',
            cursor: 'pointer',
            color: style.text,
            opacity: 0.7,
          }}
          onClick={() => alert('Đóng alert (demo)')}
        >
          ×
        </button>
      )}
    </div>
  );
}

// ========================================
// TODO 5: Avatar Component
// ========================================
function Avatar({ src, alt = 'User avatar', fallback, size = 'md' }) {
  const sizes = {
    sm: { width: 32, height: 32, fontSize: '0.8rem' },
    md: { width: 48, height: 48, fontSize: '1.1rem' },
    lg: { width: 72, height: 72, fontSize: '1.5rem' },
  };

  const style = sizes[size] || sizes.md;

  const getInitials = (text) => {
    if (!text) return '?';
    const words = text.trim().split(/\s+/);
    return (words[0][0] + (words[1]?.[0] || '')).toUpperCase();
  };

  const hasImage = src && src.trim() !== '';

  return (
    <div
      style={{
        width: style.width,
        height: style.height,
        borderRadius: '50%',
        overflow: 'hidden',
        backgroundColor: DESIGN_TOKENS.colors.gray[200],
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        position: 'relative',
        fontWeight: 600,
        color: DESIGN_TOKENS.colors.gray[700],
        fontSize: style.fontSize,
      }}
    >
      {hasImage ? (
        <img
          src={src}
          alt={alt}
          style={{
            width: '100%',
            height: '100%',
            objectFit: 'cover',
          }}
          onError={(e) => {
            e.target.style.display = 'none';
          }}
        />
      ) : (
        <span>{fallback ? getInitials(fallback) : '?'}</span>
      )}
    </div>
  );
}

// Export tất cả để sử dụng ở nơi khác nếu cần
export { Card, Badge, Alert, Avatar };

Ghi chú quan trọng khi hoàn thiện:

  • Card

    • Xử lý conditional header/footer
    • Hover effect mượt mà (translateY + shadow)
    • Sử dụng gray shades nhẹ nhàng cho header/footer
  • Badge

    • 4 variant màu sắc nhất quán
    • 2 kích thước (sm/md)
    • Border nhẹ để tăng độ tương phản
  • Alert

    • Icon emoji thay vì SVG để đơn giản
    • dismissible button (chỉ demo alert, chưa có state close thật)
    • role="alert" + semantic HTML
  • Avatar

    • Fallback initials thông minh (lấy 2 chữ cái đầu)
    • Xử lý lỗi load ảnh (ẩn img khi fail)
    • 3 kích thước rõ ràng

Checklist tự kiểm tra (đã đạt):

  • [x] Props có default values hợp lý
  • [x] Sử dụng DESIGN_TOKENS xuyên suốt
  • [x] Xử lý edge cases (no src, no fallback, empty children,…)
  • [x] Clean code, dễ đọc
  • [x] Có accessibility cơ bản (role, aria-label, alt text)
  • [x] Documentation đầy đủ trong JSDoc

Hoàn thành một mini UI library khá chuyên nghiệp chỉ với React + inline styles.
Đây là nền tảng rất tốt để sau này học CSS-in-JS (styled-components), Tailwind, hoặc CSS Modules.

Nếu muốn nâng cấp thêm, có thể thử:

  • Thêm prop icon cho Alert
  • Làm Avatar hỗ trợ status dot (online/offline)
  • Thêm loading skeleton cho Card

📊 PHẦN 4: SO SÁNH PATTERNS (30 phút)

1. Single vs Multiple Root Elements

md
| Pattern                 | Code                                      | Use When                        |
| ----------------------- | ----------------------------------------- | ------------------------------- |
| **Single Div**          | `<div><h1/><p/></div>`                    | Need actual wrapper for styling |
| **Fragment <>**         | `<><h1/><p/></>`                          | No extra DOM node needed        |
| **Fragment <Fragment>** | `<Fragment key={id}><h1/><p/></Fragment>` | Need key prop (in lists)        |
jsx
// ❌ INVALID: Multiple roots
return (
  <h1>Title</h1>
  <p>Text</p>
);

// ✅ Option 1: Div wrapper
return (
  <div>
    <h1>Title</h1>
    <p>Text</p>
  </div>
);

// ✅ Option 2: Fragment (no extra DOM)
return (
  <>
    <h1>Title</h1>
    <p>Text</p>
  </>
);

// ✅ Option 3: Array (needs keys)
return [
  <h1 key="title">Title</h1>,
  <p key="text">Text</p>
];

Decision Tree:

Need wrapper?
├─ YES, need for styling → <div>
├─ NO, just grouping → <>...</>
└─ In array/list → <Fragment key={id}>

2. Conditional Rendering Patterns

PatternSyntaxUse WhenGotchas
Ternary{condition ? <A/> : <B/>}Always show somethingBoth branches always needed
Logical AND{condition && <Component/>}Show or nothingWatch for 0 rendering
Nullish Coalescing{value ?? 'default'}Default valuesOnly null/undefined trigger default
IIFE{(() => { if... })()}Complex logicCan be verbose
jsx
// Pattern Comparison
function ConditionalExamples({ user, count }) {
  return (
    <div>
      {/* ✅ Ternary: Always 2 options */}
      {user ? <p>Hello {user.name}</p> : <p>Please login</p>}

      {/* ✅ AND: Show or nothing */}
      {user && <p>Welcome back!</p>}

      {/* ❌ PITFALL: 0 renders as "0" */}
      {count && <p>Items: {count}</p>}
      {/* If count=0, shows "0" on page! */}

      {/* ✅ FIX: Explicit boolean */}
      {count > 0 && <p>Items: {count}</p>}

      {/* ✅ Default values */}
      <p>Name: {user?.name ?? 'Guest'}</p>

      {/* ✅ Complex logic with IIFE */}
      {(() => {
        if (!user) return <p>Login required</p>;
        if (user.role === 'admin') return <AdminPanel />;
        if (user.role === 'user') return <UserDashboard />;
        return <GuestView />;
      })()}
    </div>
  );
}

3. Style Patterns

md
| Pattern               | Syntax                                        | Pros                      | Cons                       |
| --------------------- | --------------------------------------------- | ------------------------- | -------------------------- |
| **Inline Object**     | `style={{ color: 'red' }}`                    | Dynamic, scoped           | Verbose, no pseudo-classes |
| **Class**             | `className="box"`                             | Clean JSX, powerful CSS   | Global scope issues        |
| **Conditional Class** | `className={active ? 'active' : ''}`          | Simple conditionals       | String concatenation messy |
| **Template Literal**  | `` className={`box ${active && 'active'}`} `` | Readable multiple classes | Can get complex            |
jsx
// Style Pattern Examples
function StyleExamples({ isActive, theme }) {
  // ✅ Inline styles - good for dynamic values
  const dynamicStyle = {
    backgroundColor: theme.primary,
    padding: '16px',
    borderRadius: '8px',
  };

  // ✅ Conditional className
  const buttonClass = isActive ? 'button active' : 'button';

  // ✅ Template literal for multiple conditions
  const cardClass = `
    card
    ${isActive ? 'active' : ''}
    ${theme.dark ? 'dark-mode' : ''}
  `.trim();

  return (
    <div>
      {/* Inline styles */}
      <div style={dynamicStyle}>Dynamic styling</div>

      {/* Conditional class */}
      <button className={buttonClass}>Button</button>

      {/* Template literal */}
      <div className={cardClass}>Card</div>

      {/* Mix both */}
      <div
        className='box'
        style={{
          opacity: isActive ? 1 : 0.5,
          transform: isActive ? 'scale(1)' : 'scale(0.95)',
        }}
      >
        Mixed approach
      </div>
    </div>
  );
}

🧪 PHẦN 5: DEBUG LAB (20 phút)

Bug 1: Missing Key in List

jsx
// 🐛 BUG: Warning về missing keys
function TodoList() {
  const todos = ['Buy milk', 'Write code', 'Sleep'];

  return (
    <ul>
      {todos.map((todo) => (
        <li>{todo}</li> // ❌ Missing key!
      ))}
    </ul>
  );
}

// Console warning:
// Warning: Each child in a list should have a unique "key" prop

// ❓ QUESTIONS:
// 1. Tại sao React cần keys?
// 2. Index có dùng làm key được không?
// 3. Key phải unique ở đâu?

// ✅ SOLUTION:
function TodoListFixed() {
  const todos = [
    { id: 1, text: 'Buy milk' },
    { id: 2, text: 'Write code' },
    { id: 3, text: 'Sleep' },
  ];

  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.text}</li> // ✅ Unique key
      ))}
    </ul>
  );
}

// 🎓 LESSON:
// - Keys help React identify which items changed
// - Keys must be unique among siblings (not globally)
// - Avoid using index as key if list can reorder
// - Stable IDs are best (from data, not generated)

// ❌ BAD: Index as key (if list reorders)
{
  items.map((item, i) => <div key={i}>{item}</div>);
}

// ✅ GOOD: Stable unique ID
{
  items.map((item) => <div key={item.id}>{item.name}</div>);
}

// 🛡️ PREVENTION:
// - Always add key when mapping arrays
// - Use ESLint rule: react/jsx-key
// - Prefer data IDs over indices

Bug 2: Object Rendering Error

jsx
// 🐛 BUG: Objects are not valid as a React child
function UserProfile() {
  const user = {
    name: 'John',
    age: 25,
  };

  return (
    <div>
      <p>User: {user}</p> // ❌ Error!
    </div>
  );
}

// Error: Objects are not valid as a React child

// ❓ QUESTIONS:
// 1. Tại sao không render được object?
// 2. Array thì sao?
// 3. Làm sao debug nhanh?

// ✅ SOLUTIONS:

// Solution 1: Access properties
function UserProfileFixed1() {
  const user = { name: 'John', age: 25 };

  return (
    <div>
      <p>
        User: {user.name}, Age: {user.age}
      </p>{' '}
      // ✅
    </div>
  );
}

// Solution 2: JSON.stringify for debugging
function UserProfileFixed2() {
  const user = { name: 'John', age: 25 };

  return (
    <div>
      <pre>{JSON.stringify(user, null, 2)}</pre> // ✅
    </div>
  );
}

// Solution 3: Destructure
function UserProfileFixed3() {
  const user = { name: 'John', age: 25 };
  const { name, age } = user;

  return (
    <div>
      <p>
        User: {name}, Age: {age}
      </p>{' '}
      // ✅
    </div>
  );
}

// 🎓 LESSON:
// React can render:
// ✅ Strings, numbers
// ✅ JSX elements
// ✅ Arrays of above
// ❌ Plain objects
// ✅ null, undefined, boolean (render nothing)

// 🛡️ PREVENTION:
// - TypeScript will catch this at compile time
// - Always access object properties
// - Use JSON.stringify() for debugging only

Bug 3: className String Concatenation

jsx
// 🐛 BUG: className not updating correctly
function Button({ variant, size, disabled }) {
  // ❌ WRONG: String concatenation mess
  const className =
    'button' + variant === 'primary'
      ? ' button-primary'
      : '' + size === 'large'
      ? ' button-large'
      : '' + disabled
      ? ' button-disabled'
      : '';

  return <button className={className}>Click</button>;
}

// Result: className might be just "button" or have issues

// ❓ QUESTIONS:
// 1. Tại sao code trên sai?
// 2. Operator precedence vấn đề ở đâu?
// 3. Cách nào clean nhất?

// ✅ SOLUTION 1: Template literal
function ButtonFixed1({ variant, size, disabled }) {
  const className = `
    button
    ${variant === 'primary' ? 'button-primary' : ''}
    ${size === 'large' ? 'button-large' : ''}
    ${disabled ? 'button-disabled' : ''}
  `
    .trim()
    .replace(/\s+/g, ' '); // Remove extra whitespace

  return <button className={className}>Click</button>;
}

// ✅ SOLUTION 2: Array join
function ButtonFixed2({ variant, size, disabled }) {
  const classes = [
    'button',
    variant === 'primary' && 'button-primary',
    size === 'large' && 'button-large',
    disabled && 'button-disabled',
  ]
    .filter(Boolean)
    .join(' ');

  return <button className={classes}>Click</button>;
}

// ✅ SOLUTION 3: Helper function
function ButtonFixed3({ variant, size, disabled }) {
  const getClassName = () => {
    const classes = ['button'];
    if (variant === 'primary') classes.push('button-primary');
    if (size === 'large') classes.push('button-large');
    if (disabled) classes.push('button-disabled');
    return classes.join(' ');
  };

  return <button className={getClassName()}>Click</button>;
}

// 🎓 LESSON:
// - Ternary operator has low precedence
// - Use parentheses or template literals
// - Array filter(Boolean).join(' ') is clean pattern
// - Consider classnames library for complex cases

// 🛡️ PREVENTION:
// - Always use template literals or arrays
// - Test className output with console.log
// - Use classnames/clsx library in real projects

✅ PHẦN 6: TỰ ĐÁNH GIÁ (15 phút)

Knowledge Check

markdown
#### React Basics

- [ ] Tôi hiểu React là gì và tại sao dùng React
- [ ] Tôi biết cách tạo function component
- [ ] Tôi hiểu component phải return gì
- [ ] Tôi biết export/import components

#### JSX Fundamentals

- [ ] Tôi biết JSX là gì (JavaScript XML)
- [ ] Tôi có thể viết JSX correctly
- [ ] Tôi hiểu JSX được compile thành gì
- [ ] Tôi biết khi nào cần {} trong JSX

#### JSX vs HTML

- [ ] Tôi biết khác nhau class vs className
- [ ] Tôi biết khác nhau for vs htmlFor
- [ ] Tôi biết style phải là object, không phải string
- [ ] Tôi biết event handlers phải camelCase
- [ ] Tôi biết tất cả tags phải đóng đúng cách

#### JSX Expressions

- [ ] Tôi có thể nhúng variables vào JSX
- [ ] Tôi có thể gọi functions trong JSX
- [ ] Tôi biết dùng ternary operator
- [ ] Tôi biết dùng logical AND (&&)
- [ ] Tôi hiểu giới hạn của JSX expressions

#### Lists & Keys

- [ ] Tôi biết cách render arrays
- [ ] Tôi hiểu tại sao cần keys
- [ ] Tôi biết khi nào dùng index làm key
- [ ] Tôi biết keys phải unique ở đâu

#### Common Patterns

- [ ] Tôi biết các cách conditional rendering
- [ ] Tôi biết cách style components
- [ ] Tôi biết cách compose components
- [ ] Tôi có thể debug JSX errors

Code Review Checklist

markdown
#### Component Structure

- [ ] Function name là PascalCase
- [ ] Component returns JSX
- [ ] Single root element hoặc Fragment
- [ ] Properly exported

#### JSX Syntax

- [ ] className thay vì class
- [ ] htmlFor thay vì for
- [ ] style là object với camelCase properties
- [ ] Event handlers là camelCase
- [ ] Self-closing tags có />
- [ ] Proper JSX comments {/\* \*/}

#### Expressions

- [ ] Curly braces cho JavaScript expressions
- [ ] Không render objects directly
- [ ] Conditional rendering đúng pattern
- [ ] No statements (if, for) trong JSX

#### Lists

- [ ] Keys cho mọi mapped elements
- [ ] Keys là unique và stable
- [ ] Không dùng index nếu list có thể reorder

#### Code Quality

- [ ] Components là reusable
- [ ] Prop names clear và consistent
- [ ] No hardcoded values
- [ ] Clean, readable code
- [ ] Comments cho complex logic

🏠 BÀI TẬP VỀ NHÀ

Bắt buộc (30 phút)

Bài 1: Component Conversion

jsx
/**
 * Convert HTML markup sang React components:
 * 1. Create Header component
 * 2. Create ProductGrid component
 * 3. Create ProductCard component
 * 4. Compose them in App
 *
 * Focus: JSX syntax, component composition
 */
💡 Solution
jsx
// Bài tập về nhà - Bài 1: Component Conversion
// Chuyển đổi HTML thành các React component riêng biệt
// Comment bằng tiếng Việt để dễ hiểu

import React from 'react';

// =============================================
// 1. Header Component
// =============================================
function Header() {
  return (
    <header className='site-header'>
      <div className='container'>
        <div className='logo'>
          <h1>ShopName</h1>
        </div>

        <nav className='main-nav'>
          <ul>
            <li>
              <a
                href='#'
                className='active'
              >
                Trang chủ
              </a>
            </li>
            <li>
              <a href='#'>Sản phẩm</a>
            </li>
            <li>
              <a href='#'>Danh mục</a>
            </li>
            <li>
              <a href='#'>Liên hệ</a>
            </li>
          </ul>
        </nav>

        <div className='header-actions'>
          <button className='search-btn'>🔍 Tìm kiếm</button>
          <button className='cart-btn'>🛒 Giỏ hàng (0)</button>
          <button className='login-btn'>Đăng nhập</button>
        </div>
      </div>
    </header>
  );
}

// =============================================
// 2. ProductCard Component
// =============================================
function ProductCard({ product }) {
  // product là object chứa thông tin sản phẩm
  const { name, price, originalPrice, image, discount, isNew } = product;

  return (
    <div className='product-card'>
      <div className='product-image-container'>
        <img
          src={image}
          alt={name}
          className='product-image'
        />

        {/* Hiển thị nhãn giảm giá hoặc sản phẩm mới */}
        {discount > 0 && <span className='discount-badge'>-{discount}%</span>}
        {isNew && <span className='new-badge'>Mới</span>}
      </div>

      <div className='product-info'>
        <h3 className='product-name'>{name}</h3>

        <div className='product-price'>
          {discount > 0 ? (
            <>
              <span className='current-price'>
                {price.toLocaleString('vi-VN')} ₫
              </span>
              <span className='original-price'>
                {originalPrice.toLocaleString('vi-VN')} ₫
              </span>
            </>
          ) : (
            <span className='current-price'>
              {price.toLocaleString('vi-VN')} ₫
            </span>
          )}
        </div>

        <button className='add-to-cart-btn'>Thêm vào giỏ</button>
      </div>
    </div>
  );
}

// =============================================
// 3. ProductGrid Component
// =============================================
function ProductGrid() {
  // Dữ liệu mẫu - trong thực tế sẽ lấy từ API hoặc props
  const products = [
    {
      id: 1,
      name: 'Tai nghe không dây Sony WH-1000XM5',
      price: 8490000,
      originalPrice: 9990000,
      discount: 15,
      isNew: true,
      image:
        'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=500',
    },
    {
      id: 2,
      name: 'MacBook Air M2 2022',
      price: 28990000,
      originalPrice: 28990000,
      discount: 0,
      isNew: false,
      image:
        'https://images.unsplash.com/photo-1517336714731-489689fd1ca8?w=500',
    },
    {
      id: 3,
      name: 'iPhone 14 Pro 128GB',
      price: 24990000,
      originalPrice: 27990000,
      discount: 11,
      isNew: false,
      image:
        'https://images.unsplash.com/photo-1592899677977-9c10ca588bbd?w=500',
    },
    {
      id: 4,
      name: 'Bàn phím cơ Keychron K8',
      price: 2190000,
      originalPrice: 2190000,
      discount: 0,
      isNew: true,
      image:
        'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=500',
    },
  ];

  return (
    <section className='product-section'>
      <div className='container'>
        <h2>Sản phẩm nổi bật</h2>

        <div className='product-grid'>
          {products.map((product) => (
            <ProductCard
              key={product.id}
              product={product}
            />
          ))}
        </div>
      </div>
    </section>
  );
}

// =============================================
// 4. App - Component chính ghép tất cả lại
// =============================================
function App() {
  return (
    <div className='app'>
      <Header />

      <main>
        <ProductGrid />
      </main>

      {/* Có thể thêm Footer sau */}
      <footer className='site-footer'>
        <div className='container'>
          <p>© 2026 ShopName. All rights reserved.</p>
        </div>
      </footer>
    </div>
  );
}

export default App;

Bài 2: Dynamic List Rendering

jsx
/**
 * Tạo component hiển thị list of books:
 * - Map array to JSX
 * - Add proper keys
 * - Conditional rendering cho empty state
 * - Display book details (title, author, year, genre)
 */
💡 Solution
jsx
// Bài tập về nhà - Bài 2: Dynamic List Rendering
// Hiển thị danh sách sách động với:
// - Sử dụng .map() để render list
// - Key hợp lý cho mỗi item
// - Conditional rendering khi danh sách rỗng
// - Hiển thị đầy đủ thông tin sách

import React from 'react';

// =============================================
// Component chính: BookList
// =============================================
function BookList() {
  // Dữ liệu mẫu - trong thực tế có thể lấy từ API hoặc props
  const books = [
    {
      id: 1,
      title: 'Đắc Nhân Tâm',
      author: 'Dale Carnegie',
      year: 1936,
      genre: 'Kỹ năng sống',
      coverUrl:
        'https://images.unsplash.com/photo-1544947950-fa07a98d4679?w=400',
    },
    {
      id: 2,
      title: 'Nhà Giả Kim',
      author: 'Paulo Coelho',
      year: 1988,
      genre: 'Tiểu thuyết triết lý',
      coverUrl:
        'https://images.unsplash.com/photo-1543002588-bfa74002ed7e?w=400',
    },
    {
      id: 3,
      title: 'Atomic Habits',
      author: 'James Clear',
      year: 2018,
      genre: 'Phát triển bản thân',
      coverUrl:
        'https://images.unsplash.com/photo-1497633762265-9d179a990aa6?w=400',
    },
    {
      id: 4,
      title: 'Clean Code',
      author: 'Robert C. Martin',
      year: 2008,
      genre: 'Lập trình',
      coverUrl:
        'https://images.unsplash.com/photo-1532012197267-da84d127e765?w=400',
    },
  ];

  // Trường hợp danh sách rỗng để test conditional rendering
  // const books = []; // uncomment để kiểm tra empty state

  return (
    <div className='book-list-container'>
      <h2>Danh Sách Sách Nổi Bật</h2>

      {/* Conditional rendering: danh sách rỗng */}
      {books.length === 0 ? (
        <div className='empty-state'>
          <p>Chưa có sách nào trong danh sách.</p>
          <p>Hãy thêm sách mới hoặc kiểm tra lại bộ lọc!</p>
        </div>
      ) : (
        <div className='books-grid'>
          {books.map((book) => (
            <div
              key={book.id}
              className='book-card'
            >
              <div className='book-cover'>
                <img
                  src={book.coverUrl}
                  alt={`Bìa sách ${book.title}`}
                  className='book-image'
                />
              </div>

              <div className='book-info'>
                <h3 className='book-title'>{book.title}</h3>
                <p className='book-author'>
                  Tác giả: <strong>{book.author}</strong>
                </p>
                <p className='book-year'>Năm xuất bản: {book.year}</p>
                <span className='book-genre'>{book.genre}</span>
              </div>
            </div>
          ))}
        </div>
      )}

      {/* Hiển thị số lượng sách (chỉ khi có dữ liệu) */}
      {books.length > 0 && (
        <p className='book-count'>
          Tổng cộng: <strong>{books.length}</strong> cuốn sách
        </p>
      )}
    </div>
  );
}

// =============================================
// App - Component gốc để chạy demo
// =============================================
function App() {
  return (
    <div className='app'>
      <header>
        <h1>Thư Viện Sách React</h1>
      </header>

      <main>
        <BookList />
      </main>
    </div>
  );
}

export default App;

Ghi chú:

  • Sử dụng key={book.id} → đây là cách tốt nhất (unique và stable)
  • Conditional rendering với toán tử ternary để hiển thị empty state
  • Thêm ảnh bìa sách để giao diện sinh động hơn (dùng Unsplash placeholder)
  • Có thể mở rộng sau này bằng cách:
    • Thêm nút “Xem chi tiết”
    • Thêm bộ lọc theo genre/năm
    • Thêm sorting (A-Z, năm mới → cũ)

Bài 3: Conditional UI

jsx
/**
 * Tạo LoginStatus component:
 * - Show different UI based on login state
 * - Use ternary và logical AND
 * - Display user info if logged in
 * - Show login prompt if not
 */
💡 Solution
jsx
// Bài tập về nhà - Bài 3: Conditional UI
// Tạo component LoginStatus hiển thị giao diện khác nhau
// dựa vào trạng thái đăng nhập
// Sử dụng ternary operator (?) và logical AND (&&)

import React from 'react';

// =============================================
// Component LoginStatus
// =============================================
function LoginStatus() {
  // Giả lập trạng thái đăng nhập - thay đổi giá trị để test
  const isLoggedIn = true; // true → đã đăng nhập | false → chưa đăng nhập
  const user = {
    username: 'tuan_dev',
    fullName: 'Lê Văn Tuân',
    avatarUrl:
      'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400',
    role: 'Senior Developer',
    lastLogin: '17/01/2026 14:30',
  };

  // Trường hợp test chưa đăng nhập → uncomment dòng dưới
  // const isLoggedIn = false;
  // const user = null;

  return (
    <div className='login-status-container'>
      <h2>Trạng Thái Đăng Nhập</h2>

      {/* Cách 1: Sử dụng ternary operator - rõ ràng khi có 2 trạng thái đối xứng */}
      {isLoggedIn ? (
        <div className='user-logged-in'>
          <div className='user-avatar'>
            <img
              src={user.avatarUrl}
              alt={`Avatar của ${user.fullName}`}
              className='avatar-img'
            />
          </div>

          <div className='user-info'>
            <h3>Xin chào, {user.fullName}!</h3>
            <p className='username'>@{user.username}</p>
            <p className='role'>Vai trò: {user.role}</p>

            {/* Logical AND: chỉ hiển thị nếu có thông tin lastLogin */}
            {user.lastLogin && (
              <p className='last-login'>Đăng nhập lần cuối: {user.lastLogin}</p>
            )}
          </div>

          <button className='logout-btn'>Đăng xuất</button>
        </div>
      ) : (
        /* Cách 2: Giao diện khi chưa đăng nhập */
        <div className='login-prompt'>
          <div className='prompt-icon'>🔒</div>
          <h3>Bạn chưa đăng nhập</h3>
          <p>
            Đăng nhập để truy cập đầy đủ tính năng và lưu tiến trình học tập.
          </p>

          <div className='login-actions'>
            <button className='login-btn-primary'>Đăng nhập ngay</button>
            <button className='signup-btn'>Tạo tài khoản mới</button>
          </div>
        </div>
      )}

      {/* Ví dụ thêm: hiển thị thông báo chào mừng ngắn gọn (logical AND) */}
      {isLoggedIn && user.role === 'Senior Developer' && (
        <div className='welcome-badge'>
          Chào mừng quay lại, Senior Developer! 🚀
        </div>
      )}
    </div>
  );
}

// =============================================
// App - Component gốc để chạy demo
// =============================================
function App() {
  return (
    <div className='app'>
      <header>
        <h1>Ứng Dụng React - Conditional UI</h1>
      </header>

      <main>
        <LoginStatus />
      </main>
    </div>
  );
}

export default App;

Ghi chú quan trọng:

  • Sử dụng ternary operator (?:) để chọn giữa 2 giao diện hoàn toàn khác nhau (đã đăng nhập / chưa đăng nhập)
  • Sử dụng logical AND (&&) để hiển thị các phần tử phụ chỉ khi điều kiện đúng (ví dụ: lastLogin, welcome badge cho Senior Developer)
  • Dễ dàng test bằng cách thay đổi isLoggedIn = true/false
  • Có thể mở rộng sau này bằng cách:
    • Thêm loading state khi đang kiểm tra đăng nhập
    • Thêm nút “Quên mật khẩu”
    • Hiển thị thông báo “Phiên đăng nhập sắp hết hạn”

Nâng cao (60 phút)

Bài 1: Complex Component Composition

jsx
/**
 * Xây dựng Profile Page với:
 * - ProfileHeader (avatar, name, bio)
 * - ProfileStats (followers, following, posts)
 * - ProfileTabs (posts, photos, videos)
 * - ProfileContent (dynamic based on active tab)
 *
 * Requirements:
 * - Reusable components
 * - Proper prop passing
 * - Conditional rendering
 * - Clean component hierarchy
 */
💡 Solution
jsx
// Bài tập về nhà NÂNG CAO - Bài 1: Complex Component Composition
// Xây dựng một Profile Page hoàn chỉnh với:
// - ProfileHeader
// - ProfileStats
// - ProfileTabs (có active tab)
// - ProfileContent (hiển thị nội dung theo tab đang chọn)

import React, { useState } from 'react';

// =============================================
// 1. ProfileHeader
// =============================================
function ProfileHeader({ user }) {
  return (
    <div className='profile-header'>
      <div className='avatar-container'>
        <img
          src={user.avatarUrl}
          alt={`${user.name}'s avatar`}
          className='profile-avatar'
        />
      </div>

      <div className='profile-info'>
        <h1 className='profile-name'>{user.name}</h1>
        <p className='profile-username'>@{user.username}</p>

        <p className='profile-bio'>{user.bio}</p>

        <div className='profile-meta'>
          <span>📍 {user.location}</span>
          <span>•</span>
          <span>Tham gia từ {user.joinDate}</span>
        </div>
      </div>

      <button className='follow-btn'>Theo dõi</button>
    </div>
  );
}

// =============================================
// 2. ProfileStats
// =============================================
function ProfileStats({ stats }) {
  return (
    <div className='profile-stats'>
      <div className='stat-item'>
        <strong>{stats.posts}</strong>
        <span>Bài viết</span>
      </div>
      <div className='stat-item'>
        <strong>{stats.followers.toLocaleString()}</strong>
        <span>Người theo dõi</span>
      </div>
      <div className='stat-item'>
        <strong>{stats.following.toLocaleString()}</strong>
        <span>Đang theo dõi</span>
      </div>
    </div>
  );
}

// =============================================
// 3. ProfileTabs
// =============================================
function ProfileTabs({ activeTab, onTabChange }) {
  const tabs = [
    { id: 'posts', label: 'Bài viết' },
    { id: 'photos', label: 'Ảnh' },
    { id: 'videos', label: 'Video' },
    { id: 'likes', label: 'Thích' },
  ];

  return (
    <div className='profile-tabs'>
      {tabs.map((tab) => (
        <button
          key={tab.id}
          className={`tab-button ${activeTab === tab.id ? 'active' : ''}`}
          onClick={() => onTabChange(tab.id)}
        >
          {tab.label}
        </button>
      ))}
    </div>
  );
}

// =============================================
// 4. ProfileContent - hiển thị nội dung theo tab
// =============================================
function ProfileContent({ activeTab, user }) {
  // Dữ liệu mẫu cho từng tab (thực tế sẽ fetch từ API)
  const mockData = {
    posts: [
      {
        id: 1,
        content: 'Hôm nay học React rất vui! 🚀',
        likes: 42,
        comments: 8,
      },
      {
        id: 2,
        content: 'Component composition là siêu quan trọng',
        likes: 31,
        comments: 5,
      },
    ],
    photos: [
      {
        id: 1,
        url: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800',
      },
      {
        id: 2,
        url: 'https://images.unsplash.com/photo-1516321310764-9f3c9619d7d7?w=800',
      },
    ],
    videos: [
      { id: 1, title: 'Hướng dẫn useState trong 5 phút', duration: '4:52' },
      { id: 2, title: 'Tại sao nên học React năm 2026?', duration: '12:15' },
    ],
    likes: [{ id: 1, content: 'Bài viết hay về Tailwind CSS' }],
  };

  const content = mockData[activeTab] || [];

  if (content.length === 0) {
    return (
      <div className='empty-content'>
        <p>Chưa có nội dung nào trong mục này.</p>
      </div>
    );
  }

  return (
    <div className='profile-content'>
      {activeTab === 'posts' && (
        <div className='posts-list'>
          {content.map((post) => (
            <div
              key={post.id}
              className='post-item'
            >
              <p>{post.content}</p>
              <div className='post-meta'>
                <span>❤️ {post.likes}</span>
                <span>•</span>
                <span>💬 {post.comments}</span>
              </div>
            </div>
          ))}
        </div>
      )}

      {activeTab === 'photos' && (
        <div className='photos-grid'>
          {content.map((photo) => (
            <img
              key={photo.id}
              src={photo.url}
              alt='User photo'
              className='photo-item'
            />
          ))}
        </div>
      )}

      {activeTab === 'videos' && (
        <div className='videos-list'>
          {content.map((video) => (
            <div
              key={video.id}
              className='video-item'
            >
              <div className='video-thumbnail'>🎥</div>
              <div>
                <h4>{video.title}</h4>
                <span>{video.duration}</span>
              </div>
            </div>
          ))}
        </div>
      )}

      {activeTab === 'likes' && (
        <div className='likes-list'>
          {content.map((item) => (
            <div
              key={item.id}
              className='like-item'
            >
              <p>{item.content}</p>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// =============================================
// Component chính: ProfilePage
// =============================================
function ProfilePage() {
  const [activeTab, setActiveTab] = useState('posts');

  // Dữ liệu user mẫu
  const user = {
    name: 'Tuân Dev',
    username: 'tuan_dev',
    avatarUrl:
      'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400',
    bio: 'Full-stack developer | Yêu thích React & TypeScript | Đang học cách viết clean code mỗi ngày',
    location: 'TP. Hồ Chí Minh',
    joinDate: 'Tháng 3, 2024',
  };

  const stats = {
    posts: 128,
    followers: 2450,
    following: 387,
  };

  return (
    <div className='profile-page'>
      <div className='container'>
        <ProfileHeader user={user} />

        <ProfileStats stats={stats} />

        <ProfileTabs
          activeTab={activeTab}
          onTabChange={setActiveTab}
        />

        <ProfileContent
          activeTab={activeTab}
          user={user}
        />
      </div>
    </div>
  );
}

// =============================================
// App - Để chạy demo
// =============================================
function App() {
  return (
    <div className='app'>
      <ProfilePage />
    </div>
  );
}

export default App;

Ghi chú quan trọng:

  • Sử dụng useState để quản lý tab đang active (đây là phần nâng cao đầu tiên sử dụng state)
  • Truyền props một cách rõ ràng từ cha → con
  • Component được chia nhỏ, dễ tái sử dụng và bảo trì
  • Conditional rendering trong ProfileContent dựa vào activeTab
  • Dữ liệu mock → sau này có thể thay bằng API call

Thử thách thêm (nếu bạn muốn luyện tập):

  1. Thêm nút Edit Profile chỉ hiển thị khi là profile của chính mình
  2. Thêm loading skeleton khi đang tải dữ liệu
  3. Làm cho tab Photos có lightbox khi click vào ảnh
  4. Thêm nút Follow/Unfollow thay đổi text và màu khi click

Bài 2: Data Visualization

jsx
/**
 * Tạo Dashboard với charts (dùng div elements, không cần chart library):
 * - BarChart component (vertical bars)
 * - PieChart component (circular segments với CSS)
 * - StatCard component (number + label + trend)
 *
 * Use:
 * - Dynamic styling based on data
 * - Calculations in JSX
 * - Conditional colors
 */
💡 Solution
jsx
// Bài tập về nhà NÂNG CAO - Bài 2: Data Visualization
// Tạo Dashboard đơn giản với:
// - StatCard (thẻ số liệu + trend)
// - BarChart (cột dọc dùng div)
// - PieChart (vòng tròn dùng CSS conic-gradient)
// Không dùng thư viện chart bên ngoài

import React from 'react';

// =============================================
// 1. StatCard - Thẻ hiển thị số liệu + xu hướng
// =============================================
function StatCard({ title, value, change, unit = '', trendUp = true }) {
  const isPositive = change >= 0;
  const trendColor = isPositive ? '#10b981' : '#ef4444';
  const arrow = isPositive ? '↑' : '↓';

  return (
    <div className='stat-card'>
      <h3 className='stat-title'>{title}</h3>
      <div className='stat-value'>
        {value.toLocaleString()}
        {unit}
      </div>
      <div
        className='stat-trend'
        style={{ color: trendColor }}
      >
        {arrow} {Math.abs(change)}% so với tuần trước
      </div>
    </div>
  );
}

// =============================================
// 2. BarChart - Biểu đồ cột dọc đơn giản dùng div
// =============================================
function BarChart({ data, title }) {
  // Tìm giá trị lớn nhất để tính chiều cao tỉ lệ
  const maxValue = Math.max(...data.map((item) => item.value));

  return (
    <div className='bar-chart-container'>
      <h3 className='chart-title'>{title}</h3>

      <div className='bars-wrapper'>
        {data.map((item, index) => {
          // Tính % chiều cao so với max
          const heightPercent = (item.value / maxValue) * 100;
          // Màu ngẫu nhiên hoặc theo thứ tự
          const colors = [
            '#3b82f6',
            '#10b981',
            '#f59e0b',
            '#ef4444',
            '#8b5cf6',
          ];
          const barColor = colors[index % colors.length];

          return (
            <div
              key={item.label}
              className='bar-item'
            >
              <div
                className='bar'
                style={{
                  height: `${heightPercent}%`,
                  backgroundColor: barColor,
                }}
              >
                <span className='bar-value'>{item.value}</span>
              </div>
              <span className='bar-label'>{item.label}</span>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// =============================================
// 3. PieChart - Biểu đồ tròn dùng CSS conic-gradient
// =============================================
function PieChart({ data, title }) {
  // Tính tổng để tính phần trăm
  const total = data.reduce((sum, item) => sum + item.value, 0);

  // Tạo gradient string
  let cumulativePercent = 0;
  const segments = data
    .map((item) => {
      const percent = (item.value / total) * 100;
      const start = cumulativePercent;
      cumulativePercent += percent;
      return `${item.color} ${start}% ${cumulativePercent}%`;
    })
    .join(', ');

  return (
    <div className='pie-chart-container'>
      <h3 className='chart-title'>{title}</h3>

      <div className='pie-wrapper'>
        <div
          className='pie'
          style={{
            background: `conic-gradient(${segments})`,
          }}
        />

        <div className='pie-legend'>
          {data.map((item, index) => (
            <div
              key={index}
              className='legend-item'
            >
              <span
                className='legend-color'
                style={{ backgroundColor: item.color }}
              />
              <span>
                {item.label}: {item.value} (
                {((item.value / total) * 100).toFixed(1)}%)
              </span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// =============================================
// Dashboard - Ghép tất cả lại
// =============================================
function Dashboard() {
  // Dữ liệu mẫu
  const revenueData = [
    { label: 'Th 1', value: 4200000 },
    { label: 'Th 2', value: 3800000 },
    { label: 'Th 3', value: 6500000 },
    { label: 'Th 4', value: 5200000 },
    { label: 'Th 5', value: 7800000 },
    { label: 'Th 6', value: 9200000 },
  ];

  const categoryData = [
    { label: 'Điện thoại', value: 45, color: '#3b82f6' },
    { label: 'Laptop', value: 28, color: '#10b981' },
    { label: 'Phụ kiện', value: 15, color: '#f59e0b' },
    { label: 'Khác', value: 12, color: '#ef4444' },
  ];

  return (
    <div className='dashboard'>
      <header className='dashboard-header'>
        <h1>Dashboard Doanh Thu 2026</h1>
        <p>Cập nhật: {new Date().toLocaleDateString('vi-VN')}</p>
      </header>

      <div className='stats-grid'>
        <StatCard
          title='Doanh thu tháng này'
          value={9200000}
          change={18.4}
          unit=' ₫'
        />
        <StatCard
          title='Đơn hàng'
          value={384}
          change={-5.2}
        />
        <StatCard
          title='Khách hàng mới'
          value={127}
          change={12.8}
        />
        <StatCard
          title='Tỷ lệ chuyển đổi'
          value={4.8}
          change={0.9}
          unit='%'
        />
      </div>

      <div className='charts-grid'>
        <BarChart
          data={revenueData}
          title='Doanh thu theo tháng (triệu ₫)'
        />

        <PieChart
          data={categoryData}
          title='Phân bổ doanh thu theo danh mục'
        />
      </div>
    </div>
  );
}

// =============================================
// App - Component gốc
// =============================================
function App() {
  return (
    <div className='app'>
      <Dashboard />
    </div>
  );
}

export default App;

Ghi chú quan trọng:

  • Tất cả biểu đồ đều dùng HTML + CSS thuần (div, conic-gradient, percentage height)
  • StatCard dùng conditional color dựa vào trend tăng/giảm
  • BarChart tự động scale chiều cao cột dựa trên giá trị lớn nhất
  • PieChart dùng conic-gradient – rất mạnh mẽ và nhẹ
  • Dữ liệu được hard-code → sau này có thể thay bằng dữ liệu từ API

Thử thách mở rộng (nếu bạn muốn luyện thêm):

  1. Thêm tooltip khi hover vào cột/pie segment
  2. Làm animation khi load (height từ 0 → giá trị thật)
  3. Thêm nút chuyển đổi đơn vị (VND → USD)
  4. Tạo LineChart đơn giản bằng cách xếp nhiều div chồng nhau

📚 TÀI LIỆU THAM KHẢO

Bắt buộc đọc

  1. React Official Docs - Hello World

  2. React Official Docs - JSX In Depth

  3. React Official Docs - JavaScript in JSX

Đọc thêm

  1. JSX Specification

  2. React Without JSX

  3. Conditional Rendering Patterns

🔗 KẾT NỐI KIẾN THỨC

Kiến thức nền (từ Ngày 1-2)

  • Destructuring → Dùng cho props
  • Arrow functions → Define components
  • Template literals → String trong JSX
  • Array methods (map, filter) → Render lists
  • Ternary operator → Conditional rendering
  • Logical operators (&&, ||, ??) → Conditional rendering

Hướng tới (Ngày tiếp theo)

Ngày 4: Components & Props

  • Sẽ học: Passing data giữa components
  • Sẽ học: Props destructuring
  • Sẽ học: Children prop
  • Sẽ học: Component composition patterns

Ngày 5: Events & Conditional Rendering

  • Sẽ học: Event handling
  • Sẽ học: Conditional rendering deep dive
  • Sẽ học: Event binding patterns

💡 SENIOR INSIGHTS

Cân Nhắc Production

  1. JSX vs createElement
jsx
// JSX (what we write)
<Button variant='primary'>Click</Button>;

// Compiled to (what runs)
React.createElement(Button, { variant: 'primary' }, 'Click');

// Performance: JSX compilation happens at BUILD time
// No runtime cost!
  1. Component File Organization
src/
  components/
    Button/
      Button.jsx        # Component
      Button.module.css # Styles (nếu dùng CSS Modules)
      index.js          # Re-export
    Card/
      Card.jsx
      index.js
  pages/
    Dashboard.jsx
  App.jsx
  1. Performance Considerations
jsx
// ❌ Creating new objects in render
<div style={{ padding: '20px' }}>  // New object every render!

// ✅ Define outside (at top of file) or use CSS
const styles = { padding: '20px' };
function MyComponent() {
  return (
    <div style={styles}>
      Content goes here
    </div>
  );
}

Câu Hỏi Phỏng Vấn

Junior Level:

Q: JSX là gì?
A: JSX là syntax extension cho JavaScript, cho phép viết HTML-like markup
   trong JavaScript. Nó được compile thành React.createElement() calls.

Q: Khác nhau giữa class và className?
A: className là JSX equivalent của class attribute vì "class" là
   reserved keyword trong JavaScript.

Q: Tại sao cần keys khi render lists?
A: Keys giúp React identify which items changed, added, or removed.
   Điều này optimize re-rendering process.

Mid Level:

Q: JSX được compile như thế nào?
A: JSX được Babel compile thành React.createElement() calls.
   <div>Hello</div> → React.createElement('div', null, 'Hello')

Q: Khi nào nên dùng Fragment vs div wrapper?
A: Fragment (<></>) khi không cần extra DOM node.
   Div khi cần wrapper cho styling hoặc event handling.

Q: Giải thích về JSX expression limitations
A: JSX chỉ accept expressions, không phải statements.
   Có thể dùng: variables, function calls, ternary
   Không dùng: if/else, for loops, switch (phải dùng IIFE)

Senior Level:

Q: Performance implications của inline functions/objects trong JSX?
A: Inline functions/objects tạo new reference mỗi render.
   Có thể cause unnecessary re-renders cho child components.
   Solutions: useMemo, useCallback (sẽ học sau), hoặc define outside.

Q: JSX security considerations?
A: React tự động escapes values để prevent XSS attacks.
   Tuy nhiên, dangerouslySetInnerHTML bypass protection này.
   Chỉ dùng khi absolutely necessary và sanitize input.

Q: Babel JSX transform vs React 17+ JSX transform?
A: React 17+ introduced new JSX transform không require import React.
   Old: import React from 'react'
   New: Không cần (transform tự động import runtime)
   Benefit: Smaller bundle size, better performance

War Stories

Story 1: The Missing Key Bug

Vấn đề: E-commerce site có bug - khi thêm item vào cart,
        wrong item được highlighted
Root cause: Dùng index làm key trong list
Fix: Chuyển sang dùng product.id làm key
Lesson: Index as key SEEMS to work until list reorders!

Story 2: The Object Rendering Crash

Vấn đề: App crash với "Objects are not valid as React child"
Root cause: Accidentally rendering entire user object: {user}
Impact: Production down 15 minutes
Fix: {user.name} thay vì {user}
Lesson: TypeScript would have caught this at compile time

Story 3: The className Concatenation Bug

Vấn đề: Button styles không apply correctly
Root cause: 'button' + isActive ? 'active' : ''
        // Operator precedence issue!
Fix: Use template literals or array approach
Lesson: Always test dynamic className logic thoroughly

🎯 PREVIEW NGÀY MAI

Ngày 4: Components & Props

Bạn sẽ học:

  • 📦 Component communication với Props
  • 🔄 Props flow (parent → child)
  • 🎨 Props destructuring patterns
  • 👶 Children prop
  • ⚡ Prop types và validation (concepts)

Chuẩn bị:

  • [ ] Ôn lại destructuring (sẽ dùng rất nhiều!)
  • [ ] Ôn lại JSX expressions
  • [ ] Hiểu về function parameters
  • [ ] Hoàn thành bài tập về nhà

Hẹn gặp bạn ngày mai khi học cách components "talk" to each other! 🚀


📊 Tổng kết Ngày 3:

  • ✅ Đã học: React basics, JSX syntax, JSX vs HTML, Component structure
  • ✅ Đã thực hành: 5 exercises từ basic đến component library
  • ✅ Debug: 3 common JSX bugs
  • 🎯 Sẵn sàng: Học Props và component communication!

Personal tech knowledge base