Agent Skills - Phần 1: React Best Practices
Tài liệu tổng hợp 53 guideline React best practices dùng làm nguồn tham chiếu cho Agent Skills (ví dụ trong Cursor), bao gồm State, Effects, Rendering, Components, Props, Events, Forms, Hooks, Context, Performance, Testing, Accessibility và TypeScript. Mỗi guideline được đánh giá mức độ quan trọng (Cao/Trung bình/Thấp) và có ví dụ code cụ thể về nên làm và không nên làm. Nội dung dựa trên bộ guideline từ ui-ux-pro-max-skill.
Tài liệu này tổng hợp React Best Practices dùng làm nguồn tham chiếu cho Agent Skills (ví dụ trong Cursor). Nội dung dựa trên bộ guideline từ ui-ux-pro-max-skill — file react.csv.
Cập nhật: 08/02/2026.
Mức độ: 🔴 Cao · 🟡 Trung bình · 🟢 Thấp
Ví dụ code: + nên · - tránh
1. State
1.1 🟡 Use useState for local state
Simple component state nên dùng hook useState.
- Nên:
useStatecho form inputs, toggles, counters.
+ const [count, setCount] = useState(0);
- Không nên: Class components với
this.state.
- this.state = { count: 0 };
1.2 🟡 Lift state up when needed
Chia sẻ state giữa các component anh em bằng cách “lift state” lên component cha chung.
- Nên: Đưa state dùng chung lên ancestor chung.
+ // Parent giữ state và truyền xuống
- Không nên: Prop drilling qua nhiều tầng.
- // chuỗi props dài qua nhiều cấp
📖 Sharing state between components
1.3 🟡 Use useReducer for complex state
State phức tạp (nhiều trường con, logic cập nhật ph ức tạp) nên dùng reducer.
- Nên:
useReducerkhi state có nhiều sub-values hoặc cập nhật cùng lúc.
+ useReducer with action types
- Không nên: Nhiều
useStateriêng lẻ cho các giá trị liên quan.
- 5+ useState cập nhật cùng nhau
1.4 🔴 Avoid unnecessary state
Ưu tiên tính toán từ state hiện có thay vì lưu giá trị dẫn xuất vào state.
- Nên: Tính derived values trong lúc render.
+ const total = items.reduce(...);
- Không nên: Lưu giá trị có thể suy ra từ state khác.
- const [total, setTotal] = useState(0);
📖 Choosing the state structure
1.5 🟡 Initialize state lazily
Với initial state tốn kém tính toán, dùng dạng hàm để khởi tạo lazy.
- Nên:
useState(() => computeExpensive()).
+ useState(() => JSON.parse(data));
- Không nên:
useState(computeExpensive())(chạy mỗi lần render).
- useState(JSON.parse(data));
📖 Avoiding recreating the initial state
2. Effects
2.1 🔴 Clean up effects
Subscription, timer, listener phải có cleanup trong useEffect.
- Nên: Return cleanup function trong
useEffect.
+ useEffect(() => {
+ sub();
+ return unsub;
+ }, []);
- Không nên: Bỏ qua cleanup cho subscriptions/timers.
- useEffect(() => {
- subscribe();
- }, []);
📖 Connecting to an external system
2.2 🔴 Specify dependencies correctly
Mọi giá trị đọc trong effect phải có trong mảng dependency.
- Nên: Đưa đủ giá trị dùng trong effect vào deps.
+ // [value] khi dùng value trong effect
+ useEffect(() => { ... }, [value]);
- Không nên:
[]nhưng vẫn dùng props/state trong effect.
- // [] nhưng dùng props/state trong effect
- useEffect(() => { ... }, []);
📖 Specifying reactive dependencies
2.3 🔴 Avoid unnecessary effects
Không dùng effect để “transform data” hay xử lý event thuần — làm trong render hoặc event handler.
- Nên: Transform data khi render, xử lý event trực tiếp trong handler.
+ const filtered = items.filter(...);
- Không nên:
useEffectcho derived state hoặc event handling.
- useEffect(() => setFiltered(items.filter(...)), [items]);
📖 You might not need an Effect
2.4 🟡 Use refs for non-reactive values
Giá trị không cần trigger re-render (interval ID, DOM ref, instance) nên dùng useRef.
- Nên:
useRefcho interval IDs, DOM elements.
+ const intervalRef = useRef(null);
- Không nên:
useStatecho giá trị không cần render.
- const [intervalId, setIntervalId] = useState();
3. Rendering
3.1 🔴 Use keys properly
List động cần key ổn định và unique (ID), không dùng index khi thứ tự/thêm/xóa thay đổi.
- Nên: Stable IDs làm key.
+ key={item.id}
- Không nên: Array index làm key cho list động.
- key={index}
📖 Keeping list items in order with key
3.2 🟡 Memoize expensive calculations
Tính toán nặng nên bọc trong useMemo với dependency đúng.
- Nên:
useMemocho filter/sort/transform tốn kém.
+ useMemo(() => expensive(), [deps]);
- Không nên: Gọi hàm nặng mỗi lần render không cần thiết.
- const result = expensiveCalc();
3.3 🟡 Memoize callbacks passed to children
Handler truyền xuống component con (đặc biệt đã memo) nên dùng useCallback để tránh re-render không cần thiết.
- Nên:
useCallbackcho handlers truyền vào children đã memo.
+ useCallback(() => {}, [deps]);
- Không nên: Tạo function mới mỗi lần render.
- const handler = () => {};
3.4 🟢 Use React.memo wisely
Chỉ bọc component render thường xuyên với props ổn định bằng memo.
- Nên:
memocho pure component với props ổn định.
+ memo(ExpensiveList);
- Không nên:
memomọi thứ hoặc component đơn giản không cần.
- memo(SimpleButton); // cân nhắc cho component rất đơn giản
3.5 🟡 Avoid inline object/array creation in JSX
Tránh tạo object/array mới ngay trong JSX (gây re-render con không cần thiết). Định nghĩa bên ngoài hoặc memoize.
- Nên: Style/object định nghĩa ngoài component hoặc memo.
+ <div style={styles.container} />
- Không nên: Inline object trong props.
- <div style={{ margin: 10 }} />
4. Components
4.1 🟡 Keep components small and focused
Mỗi component nên có một trách nhiệm rõ ràng.
- Nên: Một concern per component.
- Không nên: Component lớn, làm nhiều việc.
<UserAvatar />
<UserName />
// Tránh: <UserCard /> với 500 dòng
4.2 🟡 Use composition over inheritance
Ưu tiên composition (children, props) thay vì kế thừa class.
- Nên:
childrenprop để linh hoạt cấu trúc.
+ <Card>{content}</Card>
- Không nên: Cây kế thừa sâu.
- class SpecialCard extends Card
4.3 🟢 Colocate related code
Đặt component và hook liên quan gần nhau (cùng thư mục/feature).
- Nên: File liên quan trong cùng directory.
+ components/User/UserCard.tsx
- Không nên: Cấu trúc phẳng với quá nhiều file tách rời.
- components/UserCard.tsx + hooks/useUser.ts xa nhau
4.4 🟢 Use fragments to avoid extra DOM
Dùng Fragment hoặc <> khi cần nhóm nhiều phần tử mà không thêm node DOM.
- Nên:
<>để nhóm không thêm wrapper.
+ <>{items.map(...)}</>
- Không nên: Thêm
<div>chỉ để bọc.
- <div>{items.map(...)}</div>
5. Props
5.1 🟢 Destructure props
Destructure props trong signature để code gọn và rõ.
- Nên: Destructure trong tham số hàm.
+ function User({ name, age }) { ... }
- Không nên: Dùng
props.name,props.valuekhắp nơi.
- function User(props) { ... }
5.2 🟢 Provide default props values
Dùng default parameters hoặc defaultProps thay vì check undefined trong component.
- Nên: Default trong destructuring.
+ function Button({ size = 'md' }) { ... }
- Không nên:
if (size === undefined) size = 'md'nhiều chỗ.
- if (size === undefined) size = 'md';
5.3 🟡 Avoid prop drilling
Data cần truyền sâu nhiều tầng nên dùng Context hoặc composition thay vì truyền props qua 5+ cấp.
- Nên: Context cho data global, composition cho UI.
+ <UserContext.Provider>
- Không nên: Truyền props qua nhiều tầng không dùng.
- <A user={u}><B user={u}><C user={u}>
📖 Passing data deeply with context
5.4 🟡 Validate props with TypeScript
Dùng TypeScript interface cho props thay vì PropTypes hoặc không validate.
- Nên:
interface Props { name: string; ... }.
+ interface ButtonProps {
+ onClick: () => void;
+ }
- Không nên:
PropTypeshoặc không có type.
- Button.propTypes = { ... };
6. Events
6.1 🟢 Use synthetic events correctly
Dùng event object chuẩn của React (đã normalize), không truy cập nativeEvent trừ khi cần.
- Nên:
e.preventDefault(),e.stopPropagation().
+ onClick={(e) => e.preventDefault()}
- Không nên: Dùng
e.nativeEventkhi không cần.
- onClick={(e) => e.nativeEvent.preventDefault()}
6.2 🟡 Avoid binding in render
Trong function component dùng arrow function hoặc useCallback; tránh .bind(this) trong render.
- Nên: Arrow function trong functional component.
+ const handleClick = () => {};
- Không nên:
bindtrong render hoặc constructor.
- this.handleClick.bind(this)
6.3 🔴 Pass event handlers not call results
Truyền reference hàm, không gọi hàm ngay (trừ khi handler trả về hàm).
- Nên:
onClick={handleClick}.
+ onClick={handleClick}
- Không nên:
onClick={handleClick()}(gọi ngay mỗi lần render).
- onClick={handleClick()}
7. Forms
7.1 🟡 Controlled components for forms
Form input nên controlled: value + onChange từ state.
- Nên:
value+onChangecho inputs.
+ <input value={val} onChange={setVal} />
- Không nên: Uncontrolled với chỉ ref (trừ khi có lý do rõ).
- <input ref={inputRef} />
📖 Controlling an input with a state variable
7.2 🟡 Handle form submission properly
Xử lý submit trong onSubmit của <form>, gọi preventDefault() trong handler.
- Nên:
onSubmitvớipreventDefault.
+ <form onSubmit={handleSubmit}>
- Không nên: Chỉ
onClicktrên nút submit.
- <button onClick={handleSubmit}>
7.3 🟡 Debounce rapid input changes
Search/filter theo input nên debounce hoặc dùng useDeferredValue để tránh lag.
- Nên:
useDeferredValuehoặc debounce cho search.
+ useDeferredValue(searchTerm);
- Không nên: Filter/request mỗi keystroke không kiểm soát.
- useEffect filtering on every change
8. Hooks
8.1 🔴 Follow rules of hooks
Chỉ gọi hooks ở top level và trong React function (component hoặc custom hook).
- Nên: Hooks luôn ở top level component/hook.
- Không nên: Hooks trong condition, loop, hoặc callback.
+ const [x, setX] = useState();
- if (cond) {
- const [x, setX] = useState();
- }
8.2 🟡 Custom hooks for reusable logic
Logic stateful dùng chung nên gom vào custom hook.
- Nên:
useCustomHookcho pattern dùng lại.
+ const { data } = useFetch(url);
- Không nên: Copy-paste
useEffect/useStategiữa các component.
- duplicate useEffect/useState trong nhiều component
📖 Reusing logic with custom hooks
8.3 🔴 Name custom hooks with use prefix
Custom hook b ắt buộc bắt đầu bằng use.
- Nên:
useFetch,useForm,useAuth.
+ function useFetch(url) { ... }
- Không nên:
fetchData,getDatacho hook.
- function fetchData(url) { ... }
9. Context
9.1 🟡 Use context for global data
Context phù hợp cho theme, auth, locale — data dùng rộng, ít thay đổi tần suất cao.
- Nên: Context cho app-wide state.
+ <ThemeContext.Provider>
- Không nên: Context cho data thay đổi rất thường xuyên (form field từng ô).
- Context cho từng giá trị form
📖 Passing data deeply with context
9.2 🟡 Split contexts by concern
Tách Context theo domain (theme, auth, i18n...) thay vì một Context khổng lồ.
- Nên:
ThemeContext+AuthContext.
+ <ThemeProvider>
+ <AuthProvider>
- Không nên: Một
AppContextchứa tất cả.
- <AppProvider value={{ theme, user, ... }}>
9.3 🔴 Memoize context values
value của Provider nên ổn định reference (dùng useMemo) để tránh re-render toàn bộ consumer không cần thiết.
- Nên:
useMemocho object value.
+ value={useMemo(() => ({ ... }), [])}
- Không nên: Tạo object mới mỗi lần render.
- value={{ user, theme }}
10. Performance
10.1 🟡 Use React DevTools Profiler
Đo và xác định bottleneck trước khi tối ưu.
- Nên: Profile trước khi optimize.
- Không nên: Đoán mò bottleneck.
10.2 🟡 Lazy load components
Route hoặc component nặng nên dùng React.lazy (và Suspense) để code splitting.
- Nên:
lazy()cho route và component nặng.
+ const Page = lazy(() => import('./Page'));
- Không nên: Import tất cả upfront.
- import Page from './Page';
10.3 🔴 Virtualize long lists
List > ~100 item nên dùng windowing (chỉ render phần visible).
- Nên:
react-windowhoặcreact-virtual.
+ <VirtualizedList items={items} />
- Không nên: Render hàng nghìn node DOM.
- {items.map(i => <Item />)}
10.4 🟢 Batch state updates
React 18 auto-batch; tránh dùng flushSync trừ khi thật cần đồng bộ với DOM.
- Nên: Để React batch các
setStateliên quan.
+ setA(1);
+ setB(2); // batched
- Không nên:
flushSynckhông cần thiết.
- flushSync(() => setA(1));
📖 Queueing a series of state updates
11. Error Handling
11.1 🔴 Use error boundaries
Bắt lỗi render trong cây component bằng Error Boundary để app không crash toàn bộ.
- Nên: ErrorBoundary bọc từng section/route.
+ <ErrorBoundary>
+ <App />
+ </ErrorBoundary>
- Không nên: Để lỗi làm sập cả app.
- No error handling
11.2 🔴 Handle async errors
Mọi async (fetch, promise) cần try/catch hoặc .catch() để không có unhandled rejection.
- Nên:
try { await fetch() } catch (e) { ... }.
+ try {
+ await fetch();
+ } catch (e) {}
- Không nên:
await fetch()không có catch.
- await fetch(); // no catch
12. Testing
12.1 🟡 Test behavior not implementation
Test hành vi (user thấy gì, làm gì), không test chi tiết implementation (state nội bộ, method).
- Nên: Test render và tương tác.
+ expect(screen.getByText('Hello'));
- Không nên: Test
component.state.namehay internal API.
- expect(component.state.name);
12.2 🟡 Use testing-library queries
Ưu tiên query theo role, label, text (accessible) thay vì data-testid cho mọi thứ.
- Nên:
getByRole,getByLabelText.
+ getByRole('button');
- Không nên:
getByTestIdcho mọi element.
- getByTestId('submit-btn');
📖 Testing Library queries priority
13. Accessibility
13.1 🔴 Use semantic HTML
Dùng đúng thẻ HTML theo mục đích: button cho hành động, nav cho điều hướng, v.v.
- Nên:
<button onClick={...}>.
+ <button onClick={...}>
- Không nên:
<div onClick={...}>cho nút bấm.
- <div onClick={...}>
13.2 🔴 Manage focus properly
Modal, dialog cần focus trap và trả focus về element trước khi mở khi đóng.
- Nên: Focus trap trong modal, return focus on close.
+ useEffect to focus input
- Không nên: Modal không quản lý focus.
- Modal without focus trap
13.3 🟡 Announce dynamic content
Nội dung thay đổi động (thông báo, toast) nên có aria-live để screen reader đọc.
- Nên:
aria-livecho cập nhật động.
+ <div aria-live="polite">{msg}</div>
- Không nên: Cập nhật im lặng cho screen reader.
- <div>{msg}</div>
13.4 🔴 Label form controls
Mọi input phải có label liên kết (htmlFor + id) hoặc aria-label; không dùng placeholder thay label.
- Nên:
<label htmlFor="email">Email</label>+ inputid="email".
+ <label htmlFor="email">Email</label>
+ <input id="email" />
- Không nên: Chỉ
<input placeholder="Email" />.
- <input placeholder="Email" />
14. TypeScript
14.1 🔴 Type component props
Mọi component nên có interface cho props, không dùng any hoặc bỏ type.
- Nên:
interface Props { name: string; ... }.
+ interface Props {
+ name: string;
+ }
- Không nên:
anyhoặc không khai báo type.
- function Component(props: any)
14.2 🟡 Type state properly
useState với kiểu phức tạp nên khai báo generic rõ ràng.
- Nên:
useState<Type>()cho state phức tạp.
+ useState<User | null>(null);
- Không nên: Để infer thành
any.
- useState(null);
14.3 🟡 Type event handlers
Dùng kiểu event của React cho handler: React.ChangeEvent<HTMLInputElement>, v.v.
- Nên:
React.ChangeEvent<HTMLInputElement>.
+ onChange: React.ChangeEvent<HTMLInputElement>
- Không nên:
Eventchung chung.
- onChange: Event
14.4 🟡 Use generics for reusable components
Component dùng lại với data generic (list, table) nên dùng generics cho type an toàn.
- Nên: Generic props cho list/table.
+ <List<T> items={T[]} />
- Không nên:
any[]cho items.
- <List items={any[]} />
15. Patterns
15.1 🟢 Container/Presentational split
Tách logic lấy data (container) và hiển thị UI (presentational) khi component phức tạp.
- Nên: Container fetch, presentational render.
+ <UserContainer>
+ <UserView />
+ </UserContainer>
- Không nên: Trộn data và UI trong một component lớn.
- <User /> vừa fetch vừa render
15.2 🟢 Render props for flexibility
Khi cần chia sẻ logic và tùy biến cách render, có thể dùng render prop.
- Nên: Render prop để tùy chỉnh render.
- Không nên: Copy-paste logic fetch/data giữa nhiều component.
<DataFetcher render={data => ...} />
📖 Passing data with a render prop
15.3 🟢 Compound components
Nhóm component liên quan (Tab + TabPanel) dùng chung state qua context thay vì prop drilling.
- Nên: Tab + TabPanel chia sẻ context.
+ <Tabs>
+ <Tab />
+ <TabPanel />
+ </Tabs>
- Không nên: Prop drilling giữa các component liên quan.
- <Tabs tabs={[]} panels={[...]} />
Tóm tắt
| Nhóm | Số guideline | Mức độ ưu tiên |
|---|---|---|
| State | 5 | 🟡–🔴 |
| Effects | 4 | 🔴 |
| Rendering | 5 | 🟡–🔴 |
| Components | 4 | 🟢–🟡 |
| Props | 4 | 🟢–🟡 |
| Events | 3 | 🟡–🔴 |
| Forms | 3 | 🟡 |
| Hooks | 3 | 🔴 |
| Context | 3 | 🟡–🔴 |
| Performance | 4 | 🟡–🔴 |
| Error Handling | 2 | 🔴 |
| Testing | 2 | 🟡 |
| Accessibility | 4 | 🔴 |
| TypeScript | 4 | 🟡–🔴 |
| Patterns | 3 | 🟢 |
Nguồn dữ liệu: react.csv — ui-ux-pro-max-skill.