summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-08-21 13:50:18 +0200
committerArmand Philippot <git@armandphilippot.com>2022-08-21 13:50:18 +0200
commita3eb518dcccaebd0f48c708c189ad2fcb07f0f73 (patch)
tree21d8350b85f47c41c382ef64ce0b91003d363a84 /src
parenta0d00743cbbdb77b27c1a3d5711407ffed5befac (diff)
fix(comments): load all comments on a post
Previously, only the first 10 comments was loaded. So I update the fetching method to retrieve all the comments on a post. Also, I choose to order comments on client side because of a bug with WPGraphQL. Finally, I renamed the Comment type to SingleComment to avoid conflict with existing types.
Diffstat (limited to 'src')
-rw-r--r--src/components/organisms/layout/comment.tsx4
-rw-r--r--src/components/organisms/layout/comments-list.fixture.tsx4
-rw-r--r--src/components/organisms/layout/comments-list.tsx12
-rw-r--r--src/pages/article/[slug].tsx8
-rw-r--r--src/services/graphql/comments.query.ts37
-rw-r--r--src/services/graphql/comments.ts144
-rw-r--r--src/ts/types/app.ts4
-rw-r--r--src/ts/types/graphql/queries.ts4
-rw-r--r--src/ts/types/raw-data.ts6
-rw-r--r--src/utils/hooks/use-comments.tsx27
10 files changed, 159 insertions, 91 deletions
diff --git a/src/components/organisms/layout/comment.tsx b/src/components/organisms/layout/comment.tsx
index f62f95c..497a04c 100644
--- a/src/components/organisms/layout/comment.tsx
+++ b/src/components/organisms/layout/comment.tsx
@@ -1,7 +1,7 @@
import Button from '@components/atoms/buttons/button';
import Link from '@components/atoms/links/link';
import Meta from '@components/molecules/layout/meta';
-import { type Comment as CommentType } from '@ts/types/app';
+import { type SingleComment } from '@ts/types/app';
import useSettings from '@utils/hooks/use-settings';
import Image from 'next/image';
import Script from 'next/script';
@@ -12,7 +12,7 @@ import CommentForm, { type CommentFormProps } from '../forms/comment-form';
import styles from './comment.module.scss';
export type CommentProps = Pick<
- CommentType,
+ SingleComment,
'approved' | 'content' | 'id' | 'meta' | 'parentId'
> &
Pick<CommentFormProps, 'Notice' | 'saveComment'> & {
diff --git a/src/components/organisms/layout/comments-list.fixture.tsx b/src/components/organisms/layout/comments-list.fixture.tsx
index 2618f77..f2a1d26 100644
--- a/src/components/organisms/layout/comments-list.fixture.tsx
+++ b/src/components/organisms/layout/comments-list.fixture.tsx
@@ -1,6 +1,6 @@
-import { Comment } from '@ts/types/app';
+import { SingleComment } from '@ts/types/app';
-export const comments: Comment[] = [
+export const comments: SingleComment[] = [
{
approved: true,
content:
diff --git a/src/components/organisms/layout/comments-list.tsx b/src/components/organisms/layout/comments-list.tsx
index 97eccb7..deb0776 100644
--- a/src/components/organisms/layout/comments-list.tsx
+++ b/src/components/organisms/layout/comments-list.tsx
@@ -1,7 +1,7 @@
-import SingleComment, {
+import Comment, {
type CommentProps,
} from '@components/organisms/layout/comment';
-import { Comment } from '@ts/types/app';
+import { SingleComment } from '@ts/types/app';
import { FC } from 'react';
import styles from './comments-list.module.scss';
@@ -9,7 +9,7 @@ export type CommentsListProps = Pick<CommentProps, 'Notice' | 'saveComment'> & {
/**
* An array of comments.
*/
- comments: Comment[];
+ comments: SingleComment[];
/**
* The maximum depth. Use `0` to not display nested comments.
*/
@@ -30,18 +30,18 @@ const CommentsList: FC<CommentsListProps> = ({
/**
* Get each comment wrapped in a list item.
*
- * @param {Comment[]} commentsList - An array of comments.
+ * @param {SingleComment[]} commentsList - An array of comments.
* @returns {JSX.Element[]} The list items.
*/
const getItems = (
- commentsList: Comment[],
+ commentsList: SingleComment[],
startLevel: number
): JSX.Element[] => {
const isLastLevel = startLevel === depth;
return commentsList.map(({ replies, ...comment }) => (
<li key={comment.id} className={styles.item}>
- <SingleComment
+ <Comment
canReply={!isLastLevel}
Notice={Notice}
saveComment={saveComment}
diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx
index 5036b5b..64610b4 100644
--- a/src/pages/article/[slug].tsx
+++ b/src/pages/article/[slug].tsx
@@ -11,12 +11,12 @@ import {
getAllArticlesSlugs,
getArticleBySlug,
} from '@services/graphql/articles';
-import { getPostComments } from '@services/graphql/comments';
+import { getAllComments } from '@services/graphql/comments';
import styles from '@styles/pages/article.module.scss';
import {
type Article,
- type Comment,
type NextPageWithLayout,
+ type SingleComment,
} from '@ts/types/app';
import { loadTranslation, type Messages } from '@utils/helpers/i18n';
import {
@@ -40,7 +40,7 @@ import { HTMLAttributes } from 'react';
import { useIntl } from 'react-intl';
type ArticlePageProps = {
- comments: Comment[];
+ comments: SingleComment[];
post: Article;
slug: string;
translation: Messages;
@@ -239,7 +239,7 @@ export const getStaticProps: GetStaticProps<ArticlePageProps> = async ({
params,
}) => {
const post = await getArticleBySlug(params!.slug as PostParams['slug']);
- const comments = await getPostComments(post.id as number);
+ const comments = await getAllComments({ contentId: post.id as number });
const translation = await loadTranslation(locale);
return {
diff --git a/src/services/graphql/comments.query.ts b/src/services/graphql/comments.query.ts
index ef93e89..5110db3 100644
--- a/src/services/graphql/comments.query.ts
+++ b/src/services/graphql/comments.query.ts
@@ -1,21 +1,32 @@
/**
* Query the comments data by post id.
*/
-export const commentsQuery = `query CommentsByPostId($contentId: ID!) {
- comments(where: {contentId: $contentId, order: ASC, orderby: COMMENT_DATE}) {
- nodes {
- approved
- author {
- node {
- gravatarUrl
- name
- url
+export const commentsQuery = `query CommentsByPostId($contentId: ID!, $first: Int = 10, $after: String = "") {
+ comments(
+ where: {contentId: $contentId}
+ first: $first
+ after: $after
+ ) {
+ edges {
+ cursor
+ node {
+ approved
+ author {
+ node {
+ gravatarUrl
+ name
+ url
+ }
}
+ content
+ databaseId
+ date
+ parentDatabaseId
}
- content
- databaseId
- date
- parentDatabaseId
+ }
+ pageInfo {
+ hasNextPage
+ endCursor
}
}
}`;
diff --git a/src/services/graphql/comments.ts b/src/services/graphql/comments.ts
index 86b6a35..41f80b3 100644
--- a/src/services/graphql/comments.ts
+++ b/src/services/graphql/comments.ts
@@ -1,46 +1,61 @@
-import { Comment } from '@ts/types/app';
+import { SingleComment } from '@ts/types/app';
import { GraphQLEdgesInput } from '@ts/types/graphql/generics';
import { SendCommentInput, SentComment } from '@ts/types/graphql/mutations';
import { ContentId } from '@ts/types/graphql/queries';
-import { RawComment } from '@ts/types/raw-data';
+import { RawComment, RawCommentsPage } from '@ts/types/raw-data';
import { getAuthorFromRawData } from '@utils/helpers/author';
import { fetchAPI } from './api';
import { sendCommentMutation } from './comments.mutation';
import { commentsQuery } from './comments.query';
-type FetchCommentsInput = ContentId &
- Pick<GraphQLEdgesInput, 'after' | 'first'>;
-
/**
- * Retrieve the comments list from GraphQL.
+ * Convert a comment from RawComment type to SingleComment type.
*
- * @param {FetchCommentsInput} variables - An object of variables.
- * @returns {Promise<RawComment[]>} The raw comments.
+ * @param {RawComment} comment - A raw comment.
+ * @returns {SingleComment} A formatted comment.
*/
-export const fetchComments = async (
- variables: FetchCommentsInput
-): Promise<RawComment[]> => {
- const response = await fetchAPI<RawComment, typeof commentsQuery>({
- query: commentsQuery,
- variables,
- });
+export const getCommentFromRawData = (comment: RawComment): SingleComment => {
+ const { author, databaseId, date, parentDatabaseId, ...data } = comment;
- return response.comments.nodes;
+ return {
+ id: databaseId,
+ meta: {
+ author: getAuthorFromRawData(author.node, 'comment'),
+ date,
+ },
+ parentId: parentDatabaseId === 0 ? undefined : parentDatabaseId,
+ replies: [],
+ ...data,
+ };
+};
+
+/**
+ * Convert an array of RawComment type to an array of SingleComment type.
+ *
+ * @param {RawComment[]} comments - The raw comments.
+ * @returns {SingleComment[]} The formatted comments.
+ */
+export const getCommentsFromRawData = (
+ comments: RawComment[]
+): SingleComment[] => {
+ return comments.map((comment) => getCommentFromRawData(comment));
};
/**
* Create a comments tree with replies.
*
- * @param {Comment[]} comments - A flatten comments list.
- * @returns {Comment[]} An array of comments with replies.
+ * @param {SingleComment[]} comments - A flatten comments list.
+ * @returns {SingleComment[]} An array of comments with replies.
*/
-export const buildCommentsTree = (comments: Comment[]): Comment[] => {
+export const buildCommentsTree = (
+ comments: SingleComment[]
+): SingleComment[] => {
type CommentsHashTable = {
- [key: string]: Comment;
+ [key: string]: SingleComment;
};
const hashTable: CommentsHashTable = Object.create(null);
- const commentsTree: Comment[] = [];
+ const commentsTree: SingleComment[] = [];
comments.forEach(
(comment) => (hashTable[comment.id] = { ...comment, replies: [] })
@@ -57,36 +72,85 @@ export const buildCommentsTree = (comments: Comment[]): Comment[] => {
return commentsTree;
};
+type FetchCommentsInput = ContentId &
+ Pick<GraphQLEdgesInput, 'after' | 'first'>;
+
/**
- * Convert a comment from RawComment to Comment type.
+ * Retrieve a raw comments page from GraphQL.
*
- * @param {RawComment} comment - A raw comment.
- * @returns {Comment} A formatted comment.
+ * @param {FetchCommentsInput} variables - An object of variables.
+ * @returns {Promise<RawCommentsPage>} A raw comments page.
*/
-export const getCommentFromRawData = (comment: RawComment): Comment => {
- const { author, databaseId, date, parentDatabaseId, ...data } = comment;
+export const fetchRawComments = async (
+ variables: FetchCommentsInput
+): Promise<RawCommentsPage> => {
+ const response = await fetchAPI<RawComment, typeof commentsQuery>({
+ query: commentsQuery,
+ variables,
+ });
return {
- id: databaseId,
- meta: {
- author: getAuthorFromRawData(author.node, 'comment'),
- date,
- },
- parentId: parentDatabaseId,
- replies: [],
- ...data,
+ comments: response.comments.edges.map((edge) => edge.node),
+ hasNextPage: response.comments.pageInfo.hasNextPage,
+ endCursor: response.comments.pageInfo.endCursor,
};
};
/**
- * Retrieve a comments list by post id.
+ * Fetch recursively all the comments on a post.
+ *
+ * @param {FetchCommentsInput} variables - An object of query variables.
+ * @param {RawCommentsPage[]} pages - An accumulator to keep track of pages.
+ * @returns {Promise<RawCommentsPage[]>} The raw comments pages.
+ */
+export const fetchAllRawCommentsPages = async (
+ variables: FetchCommentsInput,
+ pages: RawCommentsPage[] = []
+): Promise<RawCommentsPage[]> => {
+ return fetchRawComments(variables).then((page) => {
+ pages.push(page);
+
+ if (page.hasNextPage) {
+ return fetchAllRawCommentsPages(
+ { ...variables, after: page.endCursor },
+ pages
+ );
+ } else {
+ return pages;
+ }
+ });
+};
+
+/**
+ * Method to compare two comments dates and sort them from older to newest.
+ *
+ * @param {SingleComment} a - A comment.
+ * @param {SingleComment} b - Another comment.
+ * @returns {number} The difference between dates.
+ */
+export const compareCommentsDate = (
+ a: SingleComment,
+ b: SingleComment
+): number => {
+ return +new Date(a.meta.date) - +new Date(b.meta.date);
+};
+
+/**
+ * Retrieve all the comments on a post.
*
* @param {number} id - A post id.
- * @returns {Promise<Comment[]>} The comments list.
+ * @returns {Promise<SingleComment[]>} The comments list.
*/
-export const getPostComments = async (id: number): Promise<Comment[]> => {
- const rawComments = await fetchComments({ contentId: id });
- const comments = rawComments.map((comment) => getCommentFromRawData(comment));
+export const getAllComments = async ({
+ contentId,
+}: {
+ contentId: number;
+}): Promise<SingleComment[]> => {
+ const pages = await fetchAllRawCommentsPages({ contentId });
+ const comments = pages
+ .map((page) => getCommentsFromRawData(page.comments))
+ .flat()
+ .sort(compareCommentsDate);
return buildCommentsTree(comments);
};
@@ -95,7 +159,7 @@ export const getPostComments = async (id: number): Promise<Comment[]> => {
* Send a comment using GraphQL API.
*
* @param {SendCommentVars} data - The comment data.
- * @returns {Promise<SentEmail>} The mutation response.
+ * @returns {Promise<SentComment>} The mutation response.
*/
export const sendComment = async (
data: SendCommentInput
diff --git a/src/ts/types/app.ts b/src/ts/types/app.ts
index 7bf1541..c11c31b 100644
--- a/src/ts/types/app.ts
+++ b/src/ts/types/app.ts
@@ -39,13 +39,13 @@ export type CommentMeta = {
date: string;
};
-export type Comment = {
+export type SingleComment = {
approved: boolean;
content: string;
id: number;
meta: CommentMeta;
parentId?: number;
- replies: Comment[];
+ replies: SingleComment[];
};
export type Dates = {
diff --git a/src/ts/types/graphql/queries.ts b/src/ts/types/graphql/queries.ts
index cc7b62b..c29eeb3 100644
--- a/src/ts/types/graphql/queries.ts
+++ b/src/ts/types/graphql/queries.ts
@@ -96,7 +96,7 @@ export type QueriesResponseMap<T> = {
[articlesEndCursorQuery]: ArticlesResponse<EndCursorResponse>;
[articlesQuery]: ArticlesResponse<EdgesResponse<T>>;
[articlesSlugQuery]: ArticlesResponse<EdgesResponse<T>>;
- [commentsQuery]: CommentsResponse<GraphQLNodes<T>>;
+ [commentsQuery]: CommentsResponse<EdgesResponse<T>>;
[thematicBySlugQuery]: ThematicResponse<T>;
[thematicsListQuery]: ThematicsResponse<EdgesResponse<T>>;
[thematicsSlugQuery]: ThematicsResponse<EdgesResponse<T>>;
@@ -128,7 +128,7 @@ export type QueriesInputMap = {
[articlesEndCursorQuery]: QueryEdges & Search;
[articlesQuery]: QueryEdges & Search;
[articlesSlugQuery]: QueryEdges & Search;
- [commentsQuery]: ContentId;
+ [commentsQuery]: ContentId & QueryEdges;
[thematicBySlugQuery]: Slug;
[thematicsListQuery]: QueryEdges & Search;
[thematicsSlugQuery]: QueryEdges & Search;
diff --git a/src/ts/types/raw-data.ts b/src/ts/types/raw-data.ts
index ae7f7c6..022016e 100644
--- a/src/ts/types/raw-data.ts
+++ b/src/ts/types/raw-data.ts
@@ -44,6 +44,12 @@ export type RawComment = {
parentDatabaseId: number;
};
+export type RawCommentsPage = {
+ comments: RawComment[];
+ hasNextPage: boolean;
+ endCursor: string;
+};
+
export type RawCover = {
altText: string;
mediaDetails: {
diff --git a/src/utils/hooks/use-comments.tsx b/src/utils/hooks/use-comments.tsx
index cb0848b..a695bd7 100644
--- a/src/utils/hooks/use-comments.tsx
+++ b/src/utils/hooks/use-comments.tsx
@@ -1,38 +1,25 @@
-import { fetchAPI } from '@services/graphql/api';
-import {
- buildCommentsTree,
- getCommentFromRawData,
-} from '@services/graphql/comments';
-import { commentsQuery } from '@services/graphql/comments.query';
-import { Comment } from '@ts/types/app';
-import { RawComment } from '@ts/types/raw-data';
+import { getAllComments } from '@services/graphql/comments';
+import { SingleComment } from '@ts/types/app';
import useSWR from 'swr';
export type UseCommentsConfig = {
contentId?: string | number;
- fallback?: Comment[];
+ fallback?: SingleComment[];
};
/**
* Retrieve the comments of a page/article.
*
* @param {string | number} contentId - A page/article id.
- * @returns {Comment[]|undefined}
+ * @returns {SingleComment[]|undefined}
*/
const useComments = ({
contentId,
fallback,
-}: UseCommentsConfig): Comment[] | undefined => {
- const { data } = useSWR(
- contentId ? { query: commentsQuery, variables: { contentId } } : null,
- fetchAPI<RawComment, typeof commentsQuery>
- );
+}: UseCommentsConfig): SingleComment[] | undefined => {
+ const { data } = useSWR(contentId ? { contentId } : null, getAllComments);
- const comments = data?.comments.nodes.map((comment) =>
- getCommentFromRawData(comment)
- );
-
- return comments ? buildCommentsTree(comments) : fallback;
+ return data || fallback;
};
export default useComments;