Maxime Lalo 8cbd9da9bb MEP-PPD
2024-07-23 18:05:26 +02:00

106 lines
3.0 KiB
TypeScript

import React from "react";
/**
* @Documentation
* This component is used to create an infinite scroll effect.
*
* Usage:
*
* No pagination: front-end/src/components/pages/Products/index.tsx
const onSearch = useCallback((searchParam: string) => productService.search(searchParam).then((products) => setRows(buildRows(products))), []);
useEffect(() => {
productService.getLastProductSheets().then((products) => setRows(buildRows(products)));
}, []);
*
*
*
* With pagination: front-end/src/components/pages/Clients/index.tsx
*
const fetchClients = useCallback(
() =>
clientService.get(pagination.current, search.current).then((clients) => {
if (clients.length === 0) return [];
setRows((_rows) => [..._rows, ...buildRows(clients)]);
pagination.current.skip += pagination.current.take;
return clients;
}),
[],
);
const onNext = useCallback(
(release: () => void) => {
fetchClients().then((clients) => {
if (!clients.length) return console.warn("No more value to load");
release();
});
},
[fetchClients],
);
const onSearch = useCallback((searchParam: string) => {
pagination.current.skip = 0;
search.current = (searchParam && searchParam.trim()) || null;
setRows([]);
}, []);
*
*/
export type IPagination = {
take: number;
skip: number;
};
type IProps = {
offset?: number;
orientation?: "vertical" | "horizontal";
/**
* @description
* If `onNext` is set to `null`, it indicates that there is no pagination and the infinite scroll effect will not be triggered.
*/
onNext?: ((release: () => void, reset?: () => void) => Promise<void> | void) | null;
children: React.ReactElement;
};
export default function InfiniteScroll({ children, onNext, offset = 20, orientation = "vertical" }: IProps) {
const isWaiting = React.useRef<boolean>(false);
const elementRef = React.useRef<HTMLElement>();
const onChange = React.useCallback(() => {
if (!onNext) return;
const element = elementRef.current;
if (!element || isWaiting.current) return;
const { scrollTop, scrollLeft, clientHeight, clientWidth, scrollHeight, scrollWidth } = element;
let isChange = false;
if (orientation === "vertical") isChange = scrollTop + clientHeight >= scrollHeight - offset;
if (orientation === "horizontal") isChange = scrollLeft + clientWidth >= scrollWidth - offset;
if (isChange) {
isWaiting.current = true;
onNext(() => (isWaiting.current = false));
}
}, [onNext, offset, orientation]);
React.useEffect(() => onChange(), [onChange]);
React.useEffect(() => {
const observer = new MutationObserver(onChange);
elementRef.current && observer.observe(elementRef.current, { childList: true, subtree: true });
window.addEventListener("resize", onChange);
return () => {
observer.disconnect();
window.removeEventListener("resize", onChange);
};
}, [onChange]);
if (!onNext) return children;
const clonedChild = React.cloneElement(children, {
onScroll: onChange,
ref: elementRef,
});
return clonedChild;
}