1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

fixed lab infinite scroll

This commit is contained in:
Fedor Katurov 2022-12-19 18:27:51 +06:00
parent de4207ab63
commit f7cd6316f5
4 changed files with 103 additions and 23 deletions

View file

@ -1,7 +1,9 @@
import React, { FC } from 'react'; import React, { FC, useEffect, useLayoutEffect, useRef, useState } from 'react';
import Masonry from 'react-masonry-css'; import Masonry from 'react-masonry-css';
import { useScrollEnd } from '~/hooks/dom/useScrollEnd';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
const defaultColumns = { const defaultColumns = {
@ -11,12 +13,42 @@ const defaultColumns = {
interface ColumnsProps { interface ColumnsProps {
cols?: Record<number, number>; cols?: Record<number, number>;
onScrollEnd?: () => void;
hasMore?: boolean;
} }
const Columns: FC<ColumnsProps> = ({ children, cols = defaultColumns }) => ( const Columns: FC<ColumnsProps> = ({
<Masonry className={styles.wrap} breakpointCols={cols} columnClassName={styles.column}> children,
cols = defaultColumns,
onScrollEnd,
hasMore,
}) => {
const ref = useRef<HTMLDivElement>(null);
const [columns, setColumns] = useState<Element[]>([]);
useEffect(() => {
const childs = ref.current?.querySelectorAll(`.${styles.column}`);
if (!childs) return;
const timeout = setTimeout(() => setColumns([...childs]), 150);
return () => clearTimeout(timeout);
}, [ref.current]);
useScrollEnd(columns, onScrollEnd, { active: hasMore, threshold: 2 });
return (
<div ref={ref}>
<Masonry
className={styles.wrap}
breakpointCols={cols}
columnClassName={styles.column}
>
{children} {children}
</Masonry> </Masonry>
); </div>
);
};
export { Columns }; export { Columns };

View file

@ -1,11 +1,12 @@
@import "src/styles/variables"; @import 'src/styles/variables';
@import "src/styles/mixins"; @import 'src/styles/mixins';
div.wrap { div.wrap {
display: flex; display: flex;
width: 100%; width: 100%;
margin-right: 0; margin-right: 0;
padding: $gap $gap * 0.5; padding: $gap $gap * 0.5;
align-items: flex-start;
@include tablet { @include tablet {
padding: 0 $gap * 0.5; padding: 0 $gap * 0.5;

View file

@ -11,16 +11,16 @@ import styles from './styles.module.scss';
interface IProps {} interface IProps {}
const LabGrid: FC<IProps> = memo(() => { const LabGrid: FC<IProps> = memo(() => {
const { nodes, hasMore, loadMore, search, setSearch } = useLabContext(); const { nodes, hasMore, loadMore, search, setSearch, isLoading } =
useLabContext();
if (search && !nodes.length) { if (search && !nodes.length) {
return <LabNoResults resetSearch={() => setSearch('')} />; return <LabNoResults resetSearch={() => setSearch('')} />;
} }
return ( return (
<InfiniteScroll hasMore={hasMore} loadMore={loadMore}>
<div className={styles.wrap}> <div className={styles.wrap}>
<Columns> <Columns hasMore={hasMore && !isLoading} onScrollEnd={loadMore}>
{nodes.map((node) => ( {nodes.map((node) => (
<LabNode <LabNode
node={node.node} node={node.node}
@ -31,7 +31,6 @@ const LabGrid: FC<IProps> = memo(() => {
))} ))}
</Columns> </Columns>
</div> </div>
</InfiniteScroll>
); );
}); });

View file

@ -0,0 +1,48 @@
import { useCallback, useEffect, useMemo } from 'react';
import { throttle } from 'throttle-debounce';
import { useWindowSize } from './useWindowSize';
interface Options {
active?: boolean;
threshold?: number;
}
export const useScrollEnd = (
item: Element | Element[] | undefined | null,
callback?: () => void,
{ active = true, threshold = 1.5 }: Options = {},
) => {
const debouncedCallback = useMemo(
() => callback && throttle(1000, callback),
[callback],
);
const { innerHeight } = useWindowSize();
useEffect(() => {
if (!item || !active || !debouncedCallback) return;
const items = Array.isArray(item) ? item : [item];
const positions = items.map((it) => {
const { top, height } = it.getBoundingClientRect();
return top + window.scrollY + height;
});
const eventHandler = () => {
if (
!positions.some(
(it) => it < window.scrollY + window.innerHeight * threshold,
)
)
return;
debouncedCallback();
};
window.addEventListener('scroll', eventHandler);
return () => window.removeEventListener('scroll', eventHandler);
}, [item, active, innerHeight, debouncedCallback]);
};