From f7cd6316f57796aa6622f4939046a9a70e0a608a Mon Sep 17 00:00:00 2001 From: Fedor Katurov Date: Mon, 19 Dec 2022 18:27:51 +0600 Subject: [PATCH] fixed lab infinite scroll --- src/components/containers/Columns/index.tsx | 44 ++++++++++++++--- .../containers/Columns/styles.module.scss | 5 +- src/containers/lab/LabGrid/index.tsx | 29 ++++++----- src/hooks/dom/useScrollEnd.ts | 48 +++++++++++++++++++ 4 files changed, 103 insertions(+), 23 deletions(-) create mode 100644 src/hooks/dom/useScrollEnd.ts diff --git a/src/components/containers/Columns/index.tsx b/src/components/containers/Columns/index.tsx index 094beb83..b3a3759d 100644 --- a/src/components/containers/Columns/index.tsx +++ b/src/components/containers/Columns/index.tsx @@ -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; + onScrollEnd?: () => void; + hasMore?: boolean; } -const Columns: FC = ({ children, cols = defaultColumns }) => ( - - {children} - -); +const Columns: FC = ({ + children, + cols = defaultColumns, + onScrollEnd, + hasMore, +}) => { + const ref = useRef(null); + const [columns, setColumns] = useState([]); + + 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 ( +
+ + {children} + +
+ ); +}; export { Columns }; diff --git a/src/components/containers/Columns/styles.module.scss b/src/components/containers/Columns/styles.module.scss index 41c651d9..63663702 100644 --- a/src/components/containers/Columns/styles.module.scss +++ b/src/components/containers/Columns/styles.module.scss @@ -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; diff --git a/src/containers/lab/LabGrid/index.tsx b/src/containers/lab/LabGrid/index.tsx index a8b9c1a3..9ae933d6 100644 --- a/src/containers/lab/LabGrid/index.tsx +++ b/src/containers/lab/LabGrid/index.tsx @@ -11,27 +11,26 @@ import styles from './styles.module.scss'; interface IProps {} const LabGrid: FC = memo(() => { - const { nodes, hasMore, loadMore, search, setSearch } = useLabContext(); + const { nodes, hasMore, loadMore, search, setSearch, isLoading } = + useLabContext(); if (search && !nodes.length) { return setSearch('')} />; } return ( - -
- - {nodes.map((node) => ( - - ))} - -
-
+
+ + {nodes.map((node) => ( + + ))} + +
); }); diff --git a/src/hooks/dom/useScrollEnd.ts b/src/hooks/dom/useScrollEnd.ts new file mode 100644 index 00000000..bc08f14d --- /dev/null +++ b/src/hooks/dom/useScrollEnd.ts @@ -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]); +};