Hooks (@vppos/core/hooks)
Tất cả shared hooks import từ @vppos/core/hooks.
import {
useDebounce,
useStoreSelection,
usePermission,
useInfiniteScrollObserver,
} from "@vppos/core/hooks";
Danh sách hooks
useDebounce(value, delay)
Debounce một giá trị. Dùng cho search input.
const [search, setSearch] = useState("");
const debouncedSearch = useDebounce(search, 300);
// debouncedSearch chỉ cập nhật sau 300ms không gõ thêm
cảnh báo
Không dùng setTimeout thủ công. Luôn dùng useDebounce.
useStoreSelection()
Lấy thông tin cửa hàng đang chọn. Reactive — component re-render khi thay đổi.
const { storeId, selectedStore } = useStoreSelection();
cảnh báo
Không dùng getSelectedStoreId() trong component — chỉ dành cho logic ngoài React.
usePermission()
Kiểm tra quyền user hiện tại.
useColumnSettings(tableKey)
Quản lý cấu hình hiển thị cột table, persist vào localStorage.
useBrandInitializer()
Khởi tạo brand config cho cửa hàng.
useMediaQuery(query)
CSS media query responsive.
useDateRangeFilter()
Quản lý state cho filter date range (start/end date).
useInfiniteScrollObserver(options)
Hook headless để bắt sự kiện chạm đáy bằng IntersectionObserver và gọi onLoadMore.
Phù hợp cho dropdown/list/custom UI cần load thêm dữ liệu theo trang.
const { sentinelRef } = useInfiniteScrollObserver({
enabled: true,
hasMore,
isLoading: isLoadingMore,
onLoadMore: handleLoadMore,
rootMargin: "120px",
});
return (
<div className="max-h-72 overflow-auto">
{items.map((item) => (
<Row key={item.id} item={item} />
))}
<div ref={sentinelRef} />
</div>
);
Dùng cái nào khi làm infinite scroll?
- Dùng
InfiniteScrollcomponent (@vppos/core/ui/react) khi bạn muốn triển khai nhanh, UI chuẩn sẵn (loader/end message/sentinel). - Dùng
useInfiniteScrollObserverhook khi bạn cần custom layout phức tạp (dropdown riêng, virtual list, container đặc biệt).
Pattern khuyến nghị (Select / UI filters)
- Giữ
querystate ở page/component. - Debounce query bằng
useDebounce. - Gọi API theo
query + page. - Dùng infinite scroll để tăng
pagekhi chạm đáy.
const [query, setQuery] = useState("");
const [page, setPage] = useState(1);
const debouncedQuery = useDebounce(query, 300);
const { data, isFetching } = useGetServicePlansQuery({
page,
limit: 20,
search: debouncedQuery || undefined,
});
const hasMore = (data?.pagination?.page ?? 1) < (data?.pagination?.total_pages ?? 1);
const loadMore = () => {
if (!isFetching && hasMore) {
setPage((prev) => prev + 1);
}
};