diff --git a/package.json b/package.json index f2111902..d7b9aa58 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "clsx": "^1.1.1", "facepaint": "^1.2.1", "file-loader": "^6.2.0", + "intersection-observer": "^0.12.0", "prism-react-renderer": "^1.2.1", "react": "^17.0.1", "react-dom": "^17.0.1", diff --git a/src/components/WikiTable/WikiTable.tsx b/src/components/WikiTable/WikiTable.tsx index 4e38d02c..0c6a0571 100644 --- a/src/components/WikiTable/WikiTable.tsx +++ b/src/components/WikiTable/WikiTable.tsx @@ -1,25 +1,19 @@ -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; +import { WikiWord } from 'types'; import WikiTableRow from './WikiTableRow'; +import useInfiniteScroll from '../../hooks/useInfiniteScroll'; -import usePagination from '../../hooks/usePagination'; - -export default function WikiTable({ words = [] }: { words: string[] }) { - const { onPrevious, onNext, currentPage, result, isLastPage, isFirstPage } = - usePagination({ - source: words, - offset: 2, - }); +export default function WikiTable({ words = [] }: { words: WikiWord[] }) { + const { result, onNext } = useInfiniteScroll({ + source: words, + }); + const getLastRow = (index): boolean => { + return result.length - 1 == index; + }; return ( <> - {currentPage} - - @@ -28,8 +22,13 @@ export default function WikiTable({ words = [] }: { words: string[] }) { - {result.map(word => ( - + {result.map((word, index) => ( + ))}
diff --git a/src/components/WikiTable/WikiTableRow.tsx b/src/components/WikiTable/WikiTableRow.tsx index 13e8be63..6026cc90 100644 --- a/src/components/WikiTable/WikiTableRow.tsx +++ b/src/components/WikiTable/WikiTableRow.tsx @@ -1,12 +1,30 @@ -import React from 'react'; +import useIntersectionObserver from 'hooks/useIntersectionObserver'; +import React, { useEffect } from 'react'; -import { WikiWord } from '../../types'; +type wikiTableRowProps = { + description: string; + name: string; + last: boolean; + onNext: () => void; +}; +export default function WikiTableRow({ + name, + description, + last, + onNext, +}: wikiTableRowProps) { + const { ref, entry } = useIntersectionObserver({ freezeOnceVisible: true }); -export default function WikiTableRow({ name, description }: WikiWord) { - return ( - + useEffect(() => { + if (entry?.isIntersecting) onNext(); + }, [entry]); + + const content = ( + <> {name} {description} - + ); + + return last ? {content} : {content}; } diff --git a/src/hooks/useInfiniteScroll.ts b/src/hooks/useInfiniteScroll.ts new file mode 100644 index 00000000..73b98993 --- /dev/null +++ b/src/hooks/useInfiniteScroll.ts @@ -0,0 +1,51 @@ +import { useEffect, useState } from 'react'; +import { WikiWord } from 'types'; + +type Args = { + source: WikiWord[]; + limit: number; + offset: number; +}; + +type Returns = { + result: WikiWord[]; + currentOffest: number; + isLastOffset: boolean; + onNext: () => void; +}; +const usePagination = ({ + source = [], + limit = 10, + offset = 0, +}: Args): Returns => { + const getResult = (): WikiWord[] => { + const startIdx = 0; + const endIdx: number = currentOffest; + return source.slice(startIdx, endIdx); + }; + + const getMaxOffset = (): number => { + return source.length; + }; + + const [currentOffest, setCurrentOffest] = useState(limit); + + useEffect(() => { + if (currentOffest !== limit) setCurrentOffest(limit); + else getResult(); + }, [source]); + + const onNext = () => { + if (currentOffest >= getMaxOffset()) return; + setCurrentOffest(currentOffest + limit); + }; + + return { + result: getResult(), + currentOffest, + isLastOffset: currentOffest > getMaxOffset(), + onNext, + }; +}; + +export default usePagination; diff --git a/src/hooks/useIntersectionObserver.ts b/src/hooks/useIntersectionObserver.ts new file mode 100644 index 00000000..4a6ef9c5 --- /dev/null +++ b/src/hooks/useIntersectionObserver.ts @@ -0,0 +1,44 @@ +import { RefObject, useEffect, useRef, useState } from 'react'; +import 'intersection-observer'; +interface Args extends IntersectionObserverInit { + freezeOnceVisible?: boolean; + delay?: number; +} + +function useIntersectionObserver({ + threshold = 0, + root = null, + rootMargin = '0%', + freezeOnceVisible = false, + delay = 0, +}: Args): { + ref: RefObject | null; + entry: IntersectionObserverEntry | undefined; +} { + const ref = useRef(null); + const [entry, setEntry] = useState(); + + const frozen: boolean = entry?.isIntersecting && freezeOnceVisible; + const updateEntry = ([entry]: IntersectionObserverEntry[]): void => { + setTimeout(() => { + setEntry(entry); + }, delay); + }; + + useEffect(() => { + const node = ref?.current; // DOM Ref + if (frozen || !node) return; + + const observerParams = { threshold, root, rootMargin }; + const observer = new IntersectionObserver(updateEntry, observerParams); + + observer.observe(node); + return () => observer.disconnect(); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ref, threshold, root, rootMargin, frozen]); + + return { ref, entry }; +} + +export default useIntersectionObserver;