From 516f8256a4f72c7a01752d9aa4e035276fb08b51 Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Wed, 15 Dec 2021 17:08:00 +0100
Subject: chore: add pagination (load more) to blog page

---
 src/components/PostsList/PostsList.tsx | 102 +++++++++++++++++++++++++--------
 1 file changed, 77 insertions(+), 25 deletions(-)

(limited to 'src/components/PostsList/PostsList.tsx')

diff --git a/src/components/PostsList/PostsList.tsx b/src/components/PostsList/PostsList.tsx
index ca3e788..55ca232 100644
--- a/src/components/PostsList/PostsList.tsx
+++ b/src/components/PostsList/PostsList.tsx
@@ -1,36 +1,88 @@
 import Link from 'next/link';
+import useSWRInfinite from 'swr/infinite';
+import { t } from '@lingui/macro';
+import { config } from '@config/website';
+import { getPublishedPosts } from '@services/graphql/blog';
 import { ArticlePreview } from '@ts/types/articles';
+import { PageInfo } from '@ts/types/pagination';
 import styles from './PostsList.module.scss';
 
 type TitleLevel = 2 | 3 | 4 | 5 | 6;
 
-const PostsList = ({
-  posts,
-  titleLevel,
-}: {
-  posts: ArticlePreview[];
-  titleLevel: TitleLevel;
-}) => {
+type DataType = {
+  posts: ArticlePreview;
+  pageInfo: PageInfo;
+};
+
+const PostsList = ({ titleLevel }: { titleLevel: TitleLevel }) => {
   const TitleTag = `h${titleLevel}` as keyof JSX.IntrinsicElements;
 
-  const postsList = posts.map((post) => {
-    return (
-      <li key={post.id}>
-        <article>
-          <header>
-            <TitleTag>
-              <Link href={`/article/${post.slug}`}>
-                <a>{post.title}</a>
-              </Link>
-            </TitleTag>
-          </header>
-          <div dangerouslySetInnerHTML={{ __html: post.content }}></div>
-        </article>
-      </li>
-    );
-  });
-
-  return <ol className={styles.wrapper}>{postsList}</ol>;
+  const getKey = (pageIndex: number, previousData: DataType) => {
+    if (previousData && !previousData.posts) return null;
+
+    const args =
+      pageIndex === 0
+        ? { first: config.postsPerPage }
+        : {
+            first: config.postsPerPage,
+            after: previousData.pageInfo.endCursor,
+          };
+
+    return args;
+  };
+
+  const { data, error, size, setSize } = useSWRInfinite(
+    getKey,
+    getPublishedPosts
+  );
+
+  const isLoadingInitialData = !data && !error;
+  const isLoadingMore: boolean =
+    isLoadingInitialData ||
+    (size > 0 && data !== undefined && typeof data[size - 1] === 'undefined');
+
+  const getPostsList = () => {
+    if (error) return <div>{t`Failed to load.`}</div>;
+    if (!data) return <div>{t`Loading...`}</div>;
+
+    return data.map((page) => {
+      if (page.posts.length === 0) {
+        return t`No results found.`;
+      } else {
+        return page.posts.map((post) => {
+          return (
+            <li key={post.id}>
+              <article>
+                <header>
+                  <TitleTag>
+                    <Link href={`/article/${post.slug}`}>
+                      <a>{post.title}</a>
+                    </Link>
+                  </TitleTag>
+                </header>
+                <div dangerouslySetInnerHTML={{ __html: post.content }}></div>
+              </article>
+            </li>
+          );
+        });
+      }
+    });
+  };
+
+  const hasNextPage = data && data[data.length - 1].pageInfo.hasNextPage;
+
+  return (
+    <ol className={styles.wrapper}>
+      {getPostsList()}
+      {hasNextPage && (
+        <button
+          disabled={isLoadingMore}
+          type="button"
+          onClick={() => setSize(size + 1)}
+        >{t`Load more?`}</button>
+      )}
+    </ol>
+  );
 };
 
 export default PostsList;
-- 
cgit v1.2.3

