Một view fetch dữ liệu có bốn trạng thái, không phải một, và tôi thiết kế cả bốn ngay từ đầu: loading, error, empty (thành công nhưng không có dữ liệu), và success có dữ liệu. Bỏ sót empty là lỗi hay gặp — danh sách rỗng lại hiện spinner mãi hoặc màn trắng.
- Loading → skeleton, không phải spinner đơn thuần. Skeleton mô phỏng đúng bố cục sắp hiện, nên khi dữ liệu về, layout không bị nhảy (giảm CLS). Spinner giữa màn thì khi có dữ liệu bố cục đổi đột ngột.
- Chống layout shift: đặt sẵn chiều cao/khung cho ảnh và card (width/height hoặc aspect-ratio) để nội dung về không đẩy mọi thứ xuống. Đây trực tiếp là chỉ số CLS của Web Vitals.
- Error → có thông điệp rõ và nút retry. Đừng chỉ in "Something went wrong". Cho người dùng hành động: thử lại, hoặc phân biệt lỗi mạng ("kiểm tra kết nối") với 4xx/5xx.
- Empty → empty state có ngữ cảnh: giải thích vì sao trống và gợi ý bước tiếp theo (ví dụ "Chưa có đơn nào — tạo đơn đầu tiên"), thay vì bảng trống trơn.
jsx
if (isLoading) return <Skeleton />
if (isError) return <ErrorState onRetry={refetch} />
if (!data?.length) return <EmptyState /> // succeeded but no data
return <List items={data} />Mẹo: giữ khung ngoài (padding, chiều cao container) cố định qua cả bốn trạng thái để chuyển giữa chúng mượt, không giật.