Hooks & useEffect
Quy tắc dùng useEffect (dependencies, tránh vòng lặp vô hạn), memoization (useMemo, useCallback), event handler, luôn return giá trị và không dùng underscore cho method "nội bộ" (theo React/JSX Style Guide).
useEffect
Khai báo đúng dependencies
Tất cả useEffect phải khai báo đúng dependency array. Biến/hàm dùng trong effect mà đến từ props/state phải nằm trong mảng dependency để tránh stale closure và hành vi khó đoán.
// ❌ Tránh: thiếu dependency
useEffect(() => {
fetchUser(userId);
}, []);
// ✅ Tốt
useEffect(() => {
fetchUser(userId);
}, [userId]);
Hàm fetchUser từ props/context nên đưa vào dependency hoặc wrap bằng useCallback ở nơi định nghĩa.
Không gây vòng lặp render vô hạn
Effect không được gây vòng lặp vô hạn: tránh set state (hoặc trigger re-render) trong effect trong khi dependency là object/array tạo mới mỗi lần render.
// ❌ Tránh: options mới mỗi lần → effect chạy lại → setState → render → effect lại...
useEffect(() => {
setResult(fetchSomething(options));
}, [options]);
// ✅ Tốt: dependency ổn định (primitive hoặc memoized)
const optionsStable = useMemo(() => ({ page, limit }), [page, limit]);
useEffect(() => {
setResult(fetchSomething(optionsStable));
}, [optionsStable]);
Memoization
Dùng useMemo / useCallback khi cần
Khi cần tránh tính toán lại hoặc tránh tạo hàm mới mỗi lần render (truyền xuống component con hoặc dùng trong dependency của useEffect):
- useMemo — cho giá trị (object, array) phụ thuộc props/state.
- useCallback — cho hàm (event handler, callback truyền xuống con).
const sortedList = useMemo(
() => [...items].sort((a, b) => a.name.localeCompare(b.name)),
[items],
);
const handleSubmit = useCallback(() => {
submitForm(formData);
}, [formData]);
Chỉ dùng khi thực sự cần (performance hoặc dependency ổn định), tránh lạm dụng.
Event handler
Arrow function khi cần closure biến local
Khi cần truyền thêm dữ liệu vào handler (ví dụ item, index trong map), có thể dùng arrow function trong render. Lưu ý: nếu truyền xuống component tối ưu (PureComponent / React.memo), arrow tạo mới mỗi lần render có thể gây re-render không cần thiết — khi đó nên dùng useCallback hoặc truyền data qua prop khác.
function ItemList({ items, onItemAction }: Props) {
return (
<ul>
{items.map((item, index) => (
<Item key={item.id} onClick={(e) => onItemAction(e, item.id, index)} />
))}
</ul>
);
}
Handler ổn định: dùng useCallback
Handler truyền xuống component được memo (React.memo) hoặc dùng trong dependency của useEffect nên wrap bằng useCallback để reference ổn định.
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);
return <ExpensiveChild onClick={handleClick} />;
Return và đặt tên
Component luôn return giá trị
Component (function) phải return một giá trị (JSX, null, ...). Không gọi function rồi bỏ qua kết quả.
// ❌ Tránh
function MyComponent() {
<div />;
}
// ✅ Tốt
function MyComponent() {
return <div />;
}
Không dùng underscore cho method "nội bộ"
Không dùng tiền tố underscore (_handleSubmit) để thể hiện method "private". Trong JavaScript mọi thứ đều public; underscore không tạo private. Đặt tên bình thường (ví dụ handleSubmit, onSubmit) cho handler.
// ❌ Tránh
function Form() {
const _onClickSubmit = () => {
/* ... */
};
return <button onClick={_onClickSubmit}>Submit</button>;
}
// ✅ Tốt
function Form() {
const onClickSubmit = () => {
/* ... */
};
return <button onClick={onClickSubmit}>Submit</button>;
}