State ownership: server, URL hay local UI?
Phần lớn bug state trong ứng dụng Inertia không đến từ thiếu state manager. Chúng đến từ việc một giá trị có hai hoặc ba owner cùng lúc. Ví dụ xấu:useEffect để đồng bộ lẫn nhau và rất nhanh trở thành vòng lặp stale state.
1. Nguyên tắc single owner
Mỗi state quan trọng nên có một canonical owner. Các tầng khác chỉ giữ projection/draft/cache có lifecycle rõ.2. Decision tree thực chiến
Khi chuẩn bị tạouseState, hỏi:
3. Server state: đừng mirror props nếu không có lý do
Anti-pattern:products mới về nhưng rows vẫn snapshot cũ.
Đúng khi chỉ render server state
Local copy chỉ hợp lý khi có lifecycle khác
Ví dụ drag reorder chưa save:draftOrder có semantics rõ: unsaved local draft.
4. URL state: state nào user kỳ vọng share được thì nên lên URL
Ví dụ product list:5. URL hay local cho tab?
Không phải tab nào cũng giống nhau.Tab dữ liệu nên URL-owned
Tab cosmetic có thể local
6. Form state là draft, không phải server truth
Server prop:Sai: sync prop vào form mỗi render/effect
Tốt hơn
- khởi tạo draft khi component/entity identity thay đổi;
- dùng key theo entity;
- reset có chủ đích sau success.
7. History state: khác server cache
useRemember giải quyết:
useRemember để “cache products khỏi phải query lại”. Server data freshness là bài toán khác.
8. Shared props: context server-driven, không phải Redux replacement
Phù hợp:9. Derived state: thường không cần owner riêng
Sai:10. Optimistic state: temporary projection, không được chiếm canonical ownership
optimisticProductsStore song song với Inertia props, hãy dừng và xác định lại ownership.
11. Modal state: URL hay local?
Confirm delete -> local
Deep-link edit drawer -> URL
Nếu/products?edit=42 phải share được, server/URL nên biết entity đang edit.
Có thể render drawer dựa trên query prop:
12. Pagination: URL là owner, selected rows thường local
- reset selection khi page thay đổi;
- remember selection qua history;
- hoặc server-own một batch selection token nếu workflow lớn.
13. Permission state: server là owner
Server resource:14. Loading state cũng có owner
Đừng tạo một globalisLoading cho mọi async interaction.
Loading state nên cùng lifecycle với operation sinh ra nó.
15. Error state cũng có scope
Validation field error:globalError khiến UI khó biết lỗi thuộc interaction nào.
16. Race condition là ownership theo thời gian
Giả sử:- debounce;
- dùng Inertia visit lifecycle/cancellation semantics;
- commit query vào URL;
- không mirror prop vào local store.
17. Anti-pattern: prop -> state -> effect -> router -> prop loop
filters.status nếu không cần draft phase.
18. Case study: Product Index hoàn chỉnh
Phân loại state:19. Case study: Product Edit có image upload
product.image_url = previewUrl. Preview không phải persisted image.
20. Khi nào global client store thực sự hợp lý?
Có, nhưng ít hơn bạn nghĩ. Ví dụ:- rich client editor có undo/redo phức tạp độc lập navigation;
- realtime collaborative state cần merge event liên tục;
- offline-first workflow;
- highly interactive canvas/designer;
- state cross-page không phù hợp URL, server props hay history state.