React Hooks là gì?

React Hooks là một tính năng được giới thiệu từ React 16.8, cho phép bạn sử dụng state và các tính năng React khác mà không cần viết class component. Hooks giúp code trở nên ngắn gọn, dễ đọc và dễ tái sử dụng logic giữa các component.

Tại sao nên sử dụng Hooks?

  • Đơn giản hóa code: Không cần phải viết class, constructor, hay bind methods
  • Tái sử dụng logic: Dễ dàng chia sẻ stateful logic giữa các component thông qua Custom Hooks
  • Tổ chức code tốt hơn: Gom nhóm logic liên quan thay vì phân tán theo lifecycle methods
  • Dễ test: Logic được tách biệt, dễ dàng viết unit test

1. useState - Quản lý State

useState là hook cơ bản nhất, cho phép bạn thêm state vào functional component.

Cú pháp cơ bản

import { useState } from 'react';

function Counter() {
// Khai báo state với giá trị khởi tạo là 0
const [count, setCount] = useState(0);

return (
<div>
<p>Bạn đã click {count} lần</p>
<button onClick={() => setCount(count + 1)}>
Tăng
</button>
<button onClick={() => setCount(count - 1)}>
Giảm
</button>
<button onClick={() => setCount(0)}>
Reset
</button>
</div>
);
}

Functional Updates

Khi state mới phụ thuộc vào state cũ, nên sử dụng functional update để tránh bugs:

// Không nên
setCount(count + 1);

// Nên dùng
setCount(prevCount => prevCount + 1);

Lazy Initial State

Nếu initial state cần tính toán phức tạp, truyền function để chỉ chạy một lần:

// Chạy mỗi lần render
const [items, setItems] = useState(expensiveComputation());

// Chỉ chạy lần đầu
const [items, setItems] = useState(() => expensiveComputation());

2. useEffect - Side Effects

useEffect cho phép bạn thực hiện side effects trong functional component như: fetch data, subscriptions, thay đổi DOM, timers...

Cú pháp và các trường hợp sử dụng

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
// Reset states khi userId thay đổi
setLoading(true);
setError(null);

// Tạo AbortController để cancel request
const abortController = new AbortController();

async function fetchUser() {
try {
const response = await fetch('/api/users/' + userId, {
signal: abortController.signal
});

if (!response.ok) {
throw new Error('Không tìm thấy user');
}

const data = await response.json();
setUser(data);
setLoading(false);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
setLoading(false);
}
}
}

fetchUser();

// Cleanup function - chạy khi component unmount
// hoặc trước khi effect chạy lại
return () => {
abortController.abort();
};
}, [userId]); // Dependency array - effect chạy lại khi userId thay đổi

if (loading) return <div className="skeleton">Đang tải...</div>;
if (error) return <div className="error">Lỗi: {error}</div>;

return (
<div className="user-card">
<img src={user.avatar} alt={user.name} />
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}

Dependency Array

  • Không có []: Effect chạy sau mọi lần render
  • [] rỗng: Effect chỉ chạy một lần sau mount
  • [dep1, dep2]: Effect chạy khi dep1 hoặc dep2 thay đổi

3. useContext - Chia sẻ Data

useContext giúp truyền data xuống component tree mà không cần props drilling.

Tạo và sử dụng Context

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

// 1. Tạo Context
const ThemeContext = createContext();

// 2. Tạo Provider Component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');

const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};

const value = {
theme,
toggleTheme,
isDark: theme === 'dark'
};

return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}

// 3. Custom Hook để sử dụng Context
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme phải được sử dụng trong ThemeProvider');
}
return context;
}

// 4. Sử dụng trong Component
function Header() {
const { theme, toggleTheme, isDark } = useTheme();

return (
<header className={theme}>
<h1>My App</h1>
<button onClick={toggleTheme}>
{isDark ? 'Sáng' : 'Tối'}
</button>
</header>
);
}

// 5. Wrap App với Provider
function App() {
return (
<ThemeProvider>
<Header />
<Main />
<Footer />
</ThemeProvider>
);
}

4. useMemo & useCallback - Tối ưu Performance

useMemo - Cache giá trị tính toán

import { useMemo, useState } from 'react';

function ProductList({ products, searchTerm }) {
// Chỉ filter lại khi products hoặc searchTerm thay đổi
const filteredProducts = useMemo(() => {
console.log('Filtering products...');
return products.filter(product =>
product.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [products, searchTerm]);

// Tính tổng giá chỉ khi filteredProducts thay đổi
const totalPrice = useMemo(() => {
return filteredProducts.reduce((sum, p) => sum + p.price, 0);
}, [filteredProducts]);

return (
<div>
<p>Tổng: {totalPrice.toLocaleString()} VND</p>
<ul>
{filteredProducts.map(product => (
<ProductItem key={product.id} product={product} />
))}
</ul>
</div>
);
}

useCallback - Cache function reference

import { useCallback, useState, memo } from 'react';

// Component con được memo để tránh re-render không cần thiết
const ExpensiveItem = memo(function ExpensiveItem({ item, onSelect }) {
console.log('Rendering item:', item.id);
return (
<div onClick={() => onSelect(item.id)}>
{item.name}
</div>
);
});

function ItemList({ items }) {
const [selectedId, setSelectedId] = useState(null);

// Callback được cache, không tạo mới mỗi lần render
const handleSelect = useCallback((id) => {
setSelectedId(id);
console.log('Selected:', id);
}, []); // Không có dependencies vì setSelectedId là stable

return (
<div>
<p>Đã chọn: {selectedId}</p>
{items.map(item => (
<ExpensiveItem
key={item.id}
item={item}
onSelect={handleSelect}
/>
))}
</div>
);
}

5. useRef - Tham chiếu và giá trị bền vững

import { useRef, useState, useEffect } from 'react';

function VideoPlayer() {
const videoRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);

// Lưu giá trị không trigger re-render
const playCountRef = useRef(0);

const handlePlayPause = () => {
if (isPlaying) {
videoRef.current.pause();
} else {
videoRef.current.play();
playCountRef.current += 1;
console.log('Đã play:', playCountRef.current, 'lần');
}
setIsPlaying(!isPlaying);
};

return (
<div>
<video ref={videoRef} src="/video.mp4" />
<button onClick={handlePlayPause}>
{isPlaying ? 'Pause' : 'Play'}
</button>
</div>
);
}

6. Custom Hooks - Tái sử dụng Logic

Custom Hooks là cách mạnh mẽ nhất để chia sẻ logic giữa các component.

useSessionStorage Hook (Thay thế localStorage)

⚠️ LƯU Ý QUAN TRỌNG: localStorage không hoạt động trong môi trường Claude.ai artifacts. Trong production, bạn có thể sử dụng localStorage, nhưng khi test trên Claude.ai, hãy dùng sessionStorage hoặc in-memory storage.

// ✅ Phiên bản sử dụng React state (hoạt động mọi nơi)
function useInMemoryStorage(key, initialValue) {
  const [value, setValue] = useState(initialValue);
  
  return [value, setValue];
}

// ✅ Phiên bản cho production (sử dụng localStorage)
function useLocalStorage(key, initialValue) {
// Lazy initialization để tránh đọc localStorage mỗi lần render
const [value, setValue] = useState(() => {
// Kiểm tra xem có phải môi trường browser không
if (typeof window === 'undefined') {
return initialValue;
}

try {
const saved = window.localStorage.getItem(key);
return saved !== null ? JSON.parse(saved) : initialValue;
} catch {
console.error('Error reading from localStorage');
return initialValue;
}
});

// Sync với localStorage khi value thay đổi
useEffect(() => {
if (typeof window === 'undefined') return;

try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error('Error saving to localStorage:', error);
}
}, [key, value]);

return [value, setValue];
}

// Sử dụng
function Settings() {
// Trong Claude.ai artifacts, dùng useInMemoryStorage
// Trong production, dùng useLocalStorage
const [theme, setTheme] = useInMemoryStorage('theme', 'light');
const [language, setLanguage] = useInMemoryStorage('lang', 'vi');

return (
<div>
<select value={theme} onChange={e => setTheme(e.target.value)}>
<option value="light">Sáng</option>
<option value="dark">Tối</option>
</select>
<select value={language} onChange={e => setLanguage(e.target.value)}>
<option value="vi">Tiếng Việt</option>
<option value="en">English</option>
</select>
</div>
);
}

useFetch Hook

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const abortController = new AbortController();
    
    setLoading(true);
    setError(null);
    
    fetch(url, { signal: abortController.signal })
      .then(res => {
        if (!res.ok) throw new Error('Network error');
        return res.json();
      })
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(err => {
        if (err.name !== 'AbortError') {
          setError(err.message);
          setLoading(false);
        }
      });
    
    return () => abortController.abort();
  }, [url]);
  
  return { data, loading, error };
}

// Sử dụng
function UserList() {
const { data: users, loading, error } = useFetch('/api/users');

if (loading) return <Spinner />;
if (error) return <ErrorMessage message={error} />;

return (
<ul>
{users.map(user => <UserCard key={user.id} user={user} />)}
</ul>
);
}

Quy tắc sử dụng Hooks

1. Chỉ gọi Hooks ở top level - Không gọi trong loops, conditions, hay nested functions
2. Chỉ gọi Hooks từ React functions - Functional components hoặc Custom Hooks
3. Custom Hooks phải bắt đầu bằng "use" - Ví dụ: useAuth, useFetch, useLocalStorage

Kết luận

React Hooks là công cụ mạnh mẽ giúp viết React code hiệu quả hơn. Bắt đầu với useState và useEffect, sau đó mở rộng sang các hooks khác khi cần. Custom Hooks là chìa khóa để tái sử dụng logic một cách elegant.

Tips cuối cùng:

  • Sử dụng ESLint plugin eslint-plugin-react-hooks để phát hiện lỗi

  • Không lạm dụng useMemo/useCallback - chỉ dùng khi thực sự cần

  • Tách logic phức tạp thành Custom Hooks riêng

  • Khi deploy lên môi trường khác nhau, cần điều chỉnh storage strategy phù hợp