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;