summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2021-12-15 17:08:00 +0100
committerArmand Philippot <git@armandphilippot.com>2021-12-15 17:08:00 +0100
commit516f8256a4f72c7a01752d9aa4e035276fb08b51 (patch)
tree791a83b4f6cc2963246752a8ae6efbac62ebcd85 /src
parent0fa8ae55c52852c34c9143a6ec43c954c6404df1 (diff)
chore: add pagination (load more) to blog page
Diffstat (limited to 'src')
-rw-r--r--src/components/PostsList/PostsList.tsx102
-rw-r--r--src/pages/blog/index.tsx15
-rw-r--r--src/services/graphql/blog.ts6
-rw-r--r--src/ts/types/blog.ts12
4 files changed, 96 insertions, 39 deletions
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;
diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx
index 7057982..083ad97 100644
--- a/src/pages/blog/index.tsx
+++ b/src/pages/blog/index.tsx
@@ -10,10 +10,9 @@ import { NextPageWithLayout } from '@ts/types/app';
import { BlogPageProps } from '@ts/types/blog';
import { loadTranslation } from '@utils/helpers/i18n';
import PostsList from '@components/PostsList/PostsList';
+import { SWRConfig } from 'swr';
-const Blog: NextPageWithLayout<BlogPageProps> = ({ data }) => {
- const { posts, pageInfo } = data;
-
+const Blog: NextPageWithLayout<BlogPageProps> = ({ fallback }) => {
return (
<>
<Head>
@@ -21,7 +20,9 @@ const Blog: NextPageWithLayout<BlogPageProps> = ({ data }) => {
<meta name="description" content={seo.blog.description} />
</Head>
<h1>{t`Blog`}</h1>
- <PostsList posts={posts} titleLevel={2} />
+ <SWRConfig value={{ fallback }}>
+ <PostsList titleLevel={2} />
+ </SWRConfig>
</>
);
};
@@ -35,11 +36,13 @@ export const getStaticProps: GetStaticProps = async (context) => {
context.locale!,
process.env.NODE_ENV === 'production'
);
- const data = await getPublishedPosts(config.postsPerPage);
+ const data = await getPublishedPosts({ first: config.postsPerPage });
return {
props: {
- data,
+ fallback: {
+ '/api/posts': data,
+ },
translation,
},
};
diff --git a/src/services/graphql/blog.ts b/src/services/graphql/blog.ts
index 266af87..1cfdd44 100644
--- a/src/services/graphql/blog.ts
+++ b/src/services/graphql/blog.ts
@@ -85,10 +85,10 @@ export const fetchPublishedPosts: fetchPostsListReturn = async (
}
};
-export const getPublishedPosts: getPostsListReturn = async (
+export const getPublishedPosts: getPostsListReturn = async ({
first = 10,
- after = ''
-) => {
+ after = '',
+}) => {
const rawPostsList = await fetchPublishedPosts(first, after);
const postsList: ArticlePreview[] = rawPostsList.posts.edges.map((post) => {
const {
diff --git a/src/ts/types/blog.ts b/src/ts/types/blog.ts
index 345deed..366231e 100644
--- a/src/ts/types/blog.ts
+++ b/src/ts/types/blog.ts
@@ -23,11 +23,13 @@ export type fetchPostsListReturn = (
after?: string
) => Promise<PostsListResponse>;
-export type getPostsListReturn = (
- first?: number,
- after?: string
-) => Promise<PostsList>;
+type PostsListProps = {
+ first?: number;
+ after?: string;
+};
+
+export type getPostsListReturn = (props: PostsListProps) => Promise<PostsList>;
export type BlogPageProps = {
- data: PostsList;
+ fallback: PostsList;
};