Bao lâu data còn fresh. Trong window không refetch.
gcTime
Bao lâu giữ memory sau khi không dùng. Default 5m.
Optimistic
onMutate set trước, rollback khi error.
Pitfall: queryKey không stable (inline object) → refetch loạn. Quên invalidate sau mutation → stale UI. enabled:false nhưng vẫn render → conditional query phải chuẩn.
3 · Next.js Patterns (App Router)
Server Component (RSC)
Default trong app/. Render server, không state/event, không ship JS. Fetch DB trực tiếp.
Client Component
"use client" directive. Có state/effect. Hydrate trên client.
Server Action
"use server". Form submit trực tiếp tới function server — không cần API route.
Streaming + Suspense
loading.tsx cho route. <Suspense> stream phần data chậm.
// app/tasks/page.tsx — Server Component
export default async function TasksPage() {
const tasks = await db.task.findMany(); // fetch trực tiếp DB
return <TaskList tasks={tasks} />; // Client Component
}
// app/tasks/TaskList.tsx
"use client";
import { useState } from 'react';
export function TaskList({ tasks }) {
const [filter, setFilter] = useState('all');
return /* interactive UI */;
}
Caching layers: Request memoization (1 req) · Data cache (cross-request) · Full route cache (build-time) · Router cache (client). revalidateTag / revalidatePath để invalidate.
Pitfall: Pass non-serializable (function, Date raw) từ Server → Client → error. "use client" bừa → mất benefit RSC. Quên revalidate sau mutation → user thấy stale.
React Core · Controlled vs Uncontrolled · Complex Forms · AbortController · FE Async · TanStack Query Deep
⚛️ React Core
1. Hooks Rules — 2 luật bất biến
Only at top level — KHÔNG dùng trong if/for/while/nested function
Only in components or custom hooks — KHÔNG dùng trong utility function
// ❌ VIOLATIONS
function MyComponent({ flag }) {
if (flag) useState(0); // conditional → React detect được + crash
for (let i = 0; i < 5; i++) useState(i); // loop → order không stable
const click = () => useState(0); // inside event handler → wrong
}
// ✅ CORRECT
function MyComponent({ flag }) {
const [count, setCount] = useState(0); // top level
if (flag) { /* use count here OK */ }
}
Why: React track hooks bằng call order. Re-render với order khác → state mapping sai → crash. Linter react-hooks/rules-of-hooks enforce.
2. useMemo / useCallback — KHI NÀO worth it
Default position: KHÔNG dùng. Cache cũng tốn cycle CPU (check deps + lookup). Chỉ dùng khi có measurable benefit.
3 trường hợp justified:
Phép tính thật sự nặng — sort 10k items, filter phức tạp (>10ms)
Tại sao useWatch trong sub-component: chỉ component này re-render mỗi keystroke. Parent form (50 fields khác) im lặng.
6. RHF vs TanStack Form (2026 comparison)
Tiêu chí
RHF
TanStack Form
Tuổi/Maturity
2019, mature
2024, GA mới
Framework
React only
React, Vue, Solid, Svelte
TypeScript
Tốt
Tốt hơn (no generic juggling)
Validation
Zod via resolver
Zod first-class, no resolver
Async validation
Custom
First-class + debounced
Cross-field
watch + setValue tay
Field listeners declarative
Ecosystem
Khủng (shadcn, MUI)
Đang lớn nhanh
Verdict: RHF default an toàn. TanStack Form pick cho greenfield TS-heavy, cross-field deps phức tạp, hoặc unified với TanStack Query.
7. Field Types "Dị Giáo"
Field
Library 2025
File upload
Native + presigned URL → S3/R2 (bypass backend)
Image với crop
react-easy-crop
Rich text
TipTap (ProseMirror) / Lexical (Meta)
Date range
react-day-picker / MUI X DateRangePicker
Multi-select / Combobox
react-select / cmdk (shadcn)
Tags input
react-tag-input / custom với onKeyDown
Address autocomplete
Goong Maps (VN) / Google Places
Phone + country code
react-phone-number-input + libphonenumber-js
Signature
react-signature-canvas
OTP / PIN
shadcn InputOTP / react-otp-input
Color picker
react-colorful
Slider / range
Radix Slider / MUI Slider
8. Perf cho 50-60 fields
Tab-based / Stepper — lazy mount sections
useWatch trong sub-component — cô lập re-render
mode 'onBlur'/'onTouched' — không validate every keystroke
Auto-save draft — debounced localStorage / API draft endpoint
React Compiler (v19+) — auto-memoize
🛑 AbortController Deep
Core concept
AbortController = signal generator. 1 controller signal N consumers. Abort → all stop.
const controller = new AbortController();
const { signal } = controller;
// Multiple consumers
fetch(url1, { signal });
fetch(url2, { signal });
element.addEventListener('click', handler, { signal });
controller.abort(); // 1 lệnh kill TẤT CẢ
❌ Race condition KHÔNG có AbortController:
sequenceDiagram
autonumber
participant U as User
participant C as Component
participant API as API
U->>C: search "ap"
C->>API: fetch /search?q=ap (slow, 800ms)
U->>C: search "apple" (typed nhanh)
C->>API: fetch /search?q=apple (fast, 100ms)
API-->>C: 200 results for "apple" ✅
C->>C: setState(applesResults)
Note over C: User thấy kết quả "apple"
API-->>C: 200 results for "ap" (chậm về sau)
C->>C: setState(apResults) 💀
Note over U: 💀 User thấy kết quả "ap" dù đã type "apple"
✅ Fix với AbortController trong useEffect cleanup:
sequenceDiagram
autonumber
participant U as User
participant C as Component
participant Ctrl1 as Controller 1
participant Ctrl2 as Controller 2
participant API as API
U->>C: search "ap"
C->>Ctrl1: new AbortController()
C->>API: fetch /search?q=ap signal=ctrl1
U->>C: search "apple"
C->>Ctrl1: ctrl1.abort() ← cleanup useEffect
Note over API: request 1 cancelled ❌
C->>Ctrl2: new AbortController()
C->>API: fetch /search?q=apple signal=ctrl2
API-->>C: 200 results "apple" ✅
C->>C: setState(applesResults)
Note over U: ✅ User thấy đúng kết quả "apple"
await fetch(url, { signal: AbortSignal.timeout(5000) });
// Hoặc combine với user cancel:
const signal = AbortSignal.any([userController.signal, AbortSignal.timeout(5000)]);
await fetch(url, { signal });
Why functional safer: Closure capture giá trị tại thời điểm define. Functional updater bypass closure — React give latest state from queue.
3. Debouncing vs Throttling
Pattern
Fires
Use case
Debouncing
Sau khi ngừng X ms
Search, autosave
Throttling
Mỗi X ms đều đặn
Scroll, mousemove, infinite scroll
function useDebouncedValue<T>(value: T, ms: number): T {
const [d, setD] = useState(value);
useEffect(() => {
const id = setTimeout(() => setD(value), ms);
return () => clearTimeout(id);
}, [value, ms]);
return d;
}
// Sweet spot: 300-500ms cho search
🔄 TanStack Query Deep
1. Server state ≠ Client state
Server state có 7 đặc tính client state KHÔNG có: source on remote, can stale, async, multiple subscribers, loading/error states, refetch triggers, race conditions. TanStack Query là state manager dành riêng cho server state.
3 thứ quan trọng nhất: queryKey (identity) · queryFn (how to fetch) · staleTime (when to refetch).
3. isPending vs isFetching (HAY HỎI)
State
Meaning
When show
isPending
Chưa có data cache nào (first load)
Full skeleton
isFetching
Đang fetch (kể cả background)
Subtle indicator (top bar)
isError
Failed
Error UI
isSuccess
Succeeded
Render data
Stale-While-Revalidate UX: Show stale data + subtle loader thay vì blank screen mỗi refetch.
4. staleTime vs gcTime
staleTime: thời gian data được coi FRESH (không tự refetch)
gcTime: thời gian giữ data trong cache sau khi KHÔNG component nào subscribe
Data type
staleTime
gcTime
User profile (rarely changes)
5 phút
30 phút
Dashboard metrics
30 giây
5 phút
Real-time chat
0
5 phút
Config/feature flags
30 phút
1 giờ
5. Optimistic Update — 4 steps memorize
sequenceDiagram
autonumber
actor U as User
participant UI as UI
participant QC as QueryClient (cache)
participant API as Server
U->>UI: click "Like"
Note over UI,QC: onMutate begins
UI->>QC: ① CANCEL queries đang fetch
UI->>QC: ② SNAPSHOT previous (rollback context)
UI->>QC: ③ SET optimistic data (liked=true)
Note over U: User thấy UI update NGAY ⚡
UI->>API: POST /tasks/:id/like
alt ✅ Success
API-->>UI: 200
UI->>QC: ④b INVALIDATE → refetch fresh data
else ❌ Error
API-->>UI: 500
UI->>QC: ④a ROLLBACK (restore snapshot)
Note over U: User thấy UI revert + toast error
end