Data fetching và server state
Server state không chỉ là response JSON. Nó có identity, freshness, ownership, authorization, retry, invalidation và trạng thái chưa chắc chắn; vì vậy không nên copy tùy ý vào nhiều local/global stores.
1. Fetch tại đúng tầng để tránh waterfall
Fetch thủ công trong Effect hoạt động cho trường hợp đơn giản nhưng chỉ bắt đầu sau render/commit, không chạy hữu ích cho server HTML, dễ tạo parent-then-child waterfall và cần tự giải quyết cache, dedupe, race, retry. Ưu tiên route/framework loader hoặc query library tích hợp để request bắt đầu sớm, có prefetch và dùng chung kết quả.
| Cách lấy dữ liệu | Phù hợp | Trách nhiệm cần xử lý |
|---|---|---|
| Route/framework loader | Dữ liệu gắn navigation/SSR | Error boundary, revalidation, auth |
| Query cache | Interactive client server state | Query key, stale policy, invalidation |
| Server Component | Read gần data source, giảm client JS | Framework boundary, serialization, cache |
| Effect fetch | Widget nhỏ hoặc integration đặc biệt | Abort, ignore stale, cache, loading/error |
2. Cache identity, freshness và ownership
Query key phải encode mọi input ảnh hưởng response: resource ID, filter, page, locale, tenant và quyền nếu cache scope phụ thuộc chúng. Key thiếu tạo cache collision; key chứa object không ổn định có thể tạo miss hoặc refetch vô hạn tùy library.
- Fresh/stale: bao lâu dữ liệu được dùng mà không refetch?
- Retention/garbage collection: entry không còn observer sống bao lâu?
- Revalidation: refetch khi focus, reconnect, navigation hay explicit event nào?
- Deduplication: nhiều consumer cùng key chia sẻ in-flight request thế nào?
Chính sách phải theo volatility và cost của domain. Danh mục ít đổi khác dashboard realtime. Không copy response cache vào component state chỉ để “dễ sửa”; nếu cần edit, tạo draft riêng và reconcile rõ khi server snapshot đổi.
3. Request race, cancellation và error model
Khi input đổi nhanh, request cũ có thể về sau request mới. Data layer cần abort nếu có thể và bỏ qua result không còn thuộc active key. Phân biệt loading lần đầu, background refetch, empty result, recoverable error, unauthorized và offline; một boolean isLoading không diễn tả đủ UX.
4. Mutation, invalidation và optimistic UX
Sau mutation thành công, cập nhật cache bằng response authoritative hoặc invalidate đúng query liên quan. Invalidate quá rộng gây refetch storm; update quá hẹp để lại view stale. Query key và dependency map giúp xác định affected resources.
Optimistic update giảm perceived latency nhưng là dự đoán, không phải giả server đã thành công. Cần snapshot trước update, optimistic identity tạm, rollback hoặc reconcile, xử lý concurrent mutations và hiển thị trạng thái pending/failure. useOptimistic hỗ trợ optimistic projection trong lúc Action diễn ra; server validation và authorization vẫn quyết định kết quả cuối.
const [optimisticComments, addOptimistic] = useOptimistic(
comments,
(current, draft) => [...current, { ...draft, pending: true }]
);
async function submit(formData) {
const draft = { id: crypto.randomUUID(), text: formData.get('text') };
addOptimistic(draft);
await createComment(draft); // reconcile bằng server result/cache policy
}
5. Suspense, transitions và reveal UX
<Suspense> hiển thị fallback khi child suspend từ nguồn data/code có tích hợp Suspense. Fetch tùy ý trong Effect không tự kích hoạt Suspense. Boundary placement quyết định phần nào reveal cùng lúc; fallback toàn trang cho update nhỏ thường làm mất context và gây layout shift.
Transition đánh dấu navigation/update không urgent để React có thể giữ nội dung cũ thay vì thay ngay bằng fallback. Nó không làm network nhanh hơn và không thay cache/cancellation. Pending indicator cần cho user biết interaction đã được nhận; error boundary phải đặt cạnh vùng có khả năng fail và có retry/reset hợp lý.
6. Actions, progressive enhancement và kiểm thử
Form/Action APIs có thể quản pending, error, optimistic state và reset form trong runtime hỗ trợ. Với Server Functions, authorization và validation phải chạy ở server cho từng call. Native form semantics giúp progressive enhancement, nhưng duplicate submission, redirect, CSRF và replay vẫn là contract ứng dụng/server.
Test cache bằng key/freshness/invalidation; test race với response đảo thứ tự; test mutation rollback và unknown outcome; test Suspense reveal/pending/error tại boundary; test reload/back-forward và SSR hydration. Mock network ở protocol boundary để giữ behavior thật của cache thay vì mock Hook implementation.