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 { useScrollEnd } from '~/hooks/dom/useScrollEnd';
import styles from './styles.module.scss';
const defaultColumns = {
@ -11,12 +13,42 @@ const defaultColumns = {
interface ColumnsProps {
cols?: Record<number, number>;
onScrollEnd?: () => void;
hasMore?: boolean;
}
const Columns: FC<ColumnsProps> = ({ children, cols = defaultColumns }) => (
<Masonry className={styles.wrap} breakpointCols={cols} columnClassName={styles.column}>
{children}
</Masonry>
);
const Columns: FC<ColumnsProps> = ({
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}
</Masonry>
</div>
);
};
export { Columns };

View file

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

View file

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

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]);
};