Skip to content

Commit

Permalink
Use tanstack query instead nextjs features for static build
Browse files Browse the repository at this point in the history
  • Loading branch information
marshallku committed Feb 12, 2024
1 parent 3b43776 commit 21e8ef0
Show file tree
Hide file tree
Showing 13 changed files with 138 additions and 87 deletions.
60 changes: 0 additions & 60 deletions apps/blog/app/[category]/[...slug]/action.ts

This file was deleted.

2 changes: 2 additions & 0 deletions apps/blog/app/[category]/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { classNames, formatDate } from "@marshallku/utils";
import MDXComponents from "#components/MDXComponents";
import InteractPost from "#components/InteractPost";
import Image from "#components/Image";
import PostComment from "#components/PostComment";
import PostList from "#components/PostList";
import PrevNextPost from "#components/PrevNextPost";
import Typography from "#components/Typography";
Expand Down Expand Up @@ -169,6 +170,7 @@ export default async function Post({ params: { category, slug } }: PostProps) {
</Typography>
</main>
<InteractPost className={cx("__interact")} title={post.data.title} slug={post.slug} />
<PostComment slug={`/${postSlug}`} />
<PrevNextPost previousPost={posts[postIndex + 1]} nextPost={posts[postIndex - 1]} />
<aside className={cx("-related-articles")}>
<Typography
Expand Down
1 change: 1 addition & 0 deletions apps/blog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@marshallku/utils": "workspace:^",
"@next/eslint-plugin-next": "^14.1.0",
"@next/third-parties": "^14.1.0",
"@tanstack/react-query": "^5.20.1",
"copy2clip": "^1.1.1",
"gray-matter": "^4.0.3",
"image-size": "^1.1.1",
Expand Down
8 changes: 2 additions & 6 deletions apps/blog/src/api/comment/api.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { request } from "#api/instance";
import { request } from "#api/instance.client";
import { Comment, CommentListResponse } from "./types";

export async function getComments(slug: string) {
return request<CommentListResponse>(`/comment/list?postSlug=${encodeURIComponent(slug)}`, {
next: {
tags: [slug],
},
});
return request<CommentListResponse>(`/comment/list?postSlug=${encodeURIComponent(slug)}`);
}

export async function postComment(data: Omit<Comment, "_id" | "createdAt" | "byPostAuthor">) {
Expand Down
18 changes: 18 additions & 0 deletions apps/blog/src/api/comment/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"use client";

import { useMutation, useQueryClient, useSuspenseQuery } from "@tanstack/react-query";
import { getComments, postComment } from "./api";

export const useCommentList = (slug: string) =>
useSuspenseQuery({
queryKey: ["commentList", slug],
queryFn: () => getComments(slug),
});

export const usePostComment = (slug: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (comment: Parameters<typeof postComment>[0]) => postComment(comment),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["commentList", slug] }),
});
};
1 change: 0 additions & 1 deletion apps/blog/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from "./comment/api";
export * from "./comment/types";
export * from "./instance";
23 changes: 23 additions & 0 deletions apps/blog/src/api/instance.client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use client";

import httpClient, { HTTPClient } from "#utils/httpClient";

export const request: HTTPClient<unknown> = httpClient({
baseUrl: process.env.NEXT_PUBLIC_API_URL,
headers: {
"Content-Type": "application/json",
},
credentials: "include",
cache: "no-store",
interceptors: {
async response(response) {
try {
return await response.json();
} catch {
// eslint-disable-next-line no-console
console.error("Failed to parse response body as JSON.");
return null;
}
},
},
});
File renamed without changes.
65 changes: 51 additions & 14 deletions apps/blog/src/components/CommentForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,70 @@
"use client";

import { useFormState } from "react-dom";
import { FormEventHandler, useCallback, useRef, useState } from "react";
import { Icon } from "@marshallku/icon";
import { classNames } from "@marshallku/utils";
import { submitComment } from "app/[category]/[...slug]/action";
import Button from "#components/Button";
import styles from "./index.module.scss";
import Typography from "#components/Typography";
import CommentAvatar from "#components/CommentAvatar";
import { useState } from "react";
import { Icon } from "@marshallku/icon";
import styles from "./index.module.scss";
import { usePostComment } from "#api/comment/queries";

const cx = classNames(styles, "comment-form");
interface CommentFormProps {
slug: string;
}

const initialState = {
message: "",
};
const cx = classNames(styles, "comment-form");

function CommentForm() {
const [state, formAction] = useFormState(submitComment, initialState);
function CommentForm({ slug }: CommentFormProps) {
const [name, setName] = useState("");
const [body, setBody] = useState("");
const formRef = useRef<HTMLFormElement>(null);
const { mutate } = usePostComment(slug);

const handleSubmit: FormEventHandler<HTMLFormElement> = useCallback(
(event) => {
event.preventDefault();

const formData = new FormData(event.currentTarget);
const body = formData.get("body");

if (!body || typeof body !== "string" || body.trim() === "") {
return {
message: "내용을 입력해 주세요.",
};
}

const korean = /[\u3131-\uD79D]/giu;

if (!korean.test(body)) {
return {
message: "한글을 입력해 주세요.",
};
}

const data = {
name: (formData.get("name") as string) || "익명",
postSlug: slug,
password: formData.get("password") as string,
email: formData.get("email") as string,
url: formData.get("url") as string,
body,
parentCommentId: formData.get("parentCommentId") as string,
};

mutate(data);
formRef.current?.reset();
setName("");
setBody("");
},
[slug],
);

return (
<div className={cx()}>
<figure className={cx("__avatar")}>
<CommentAvatar name={name} />
</figure>
<form action={formAction} className={cx("__form")}>
<form className={cx("__form")} onSubmit={handleSubmit} ref={formRef}>
<Typography variant="c1" marginBottom={16}>
댓글을 남겨주세요. (이메일 주소는 공개되지 않습니다.)
</Typography>
Expand All @@ -50,7 +88,6 @@ function CommentForm() {
setBody(value);
}}
/>
{state.message && <Typography variant="c2">{state.message}</Typography>}
<div className={cx("__buttons")}>
<button type="submit" disabled={!body} className={cx("__submit")}>
<Icon name="arrow-downward" size={24} />
Expand Down
8 changes: 7 additions & 1 deletion apps/blog/src/components/CommentList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
"use client";

import { Fragment } from "react";
import { classNames } from "@marshallku/utils";
import { type CommentListResponse } from "#api";
import { useCommentList } from "#api/comment/queries";
import CommentBubble from "#components/CommentBubble";
import { PostCommentProps } from "#components/PostComment";
import styles from "./index.module.scss";

export interface CommentListProps {
Expand All @@ -10,7 +14,9 @@ export interface CommentListProps {

const cx = classNames(styles, "comment-list");

function CommentList({ data }: CommentListProps) {
function CommentList({ slug }: Pick<PostCommentProps, "slug">) {
const { data } = useCommentList(slug);

return (
<ul className={cx()}>
{data.map(({ replies, ...comment }) => (
Expand Down
22 changes: 17 additions & 5 deletions apps/blog/src/components/PostComment/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
"use client";

import { Suspense, useMemo } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ErrorBoundary } from "@marshallku/react-error-boundary";
import { classNames } from "@marshallku/utils";
import { type CommentListResponse } from "#api";
import CommentForm from "#components/CommentForm";
import CommentList from "#components/CommentList";
import styles from "./index.module.scss";

export interface PostCommentProps {
data?: CommentListResponse;
slug: string;
}

const cx = classNames(styles, "post-comment");

function PostComment({ data }: PostCommentProps) {
function PostComment({ slug }: PostCommentProps) {
const queryClient = useMemo(() => new QueryClient(), []);

return (
<div className={cx()}>
<CommentForm />
{data && <CommentList data={data} />}
<QueryClientProvider client={queryClient}>
<CommentForm slug={slug} />
<ErrorBoundary fallback={null}>
<Suspense fallback={null}>
<CommentList slug={slug} />
</Suspense>
</ErrorBoundary>
</QueryClientProvider>
</div>
);
}
Expand Down
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"sonarsource",
"Svgwr",
"tailwindcss",
"tanstack",
"tistory",
"travisci",
"treeshake",
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 21e8ef0

Please sign in to comment.