Skip to content

Commit 133dcda

Browse files
authored
Merge pull request #39 from nettee-space/feature/post-list
무한스크롤 기능 관련 코드 리뷰 반영
2 parents db86d57 + e105048 commit 133dcda

File tree

9 files changed

+71
-79
lines changed

9 files changed

+71
-79
lines changed

apps/web/app/page.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
1+
import { Button } from '@workspace/ui/components/button';
12
import Link from 'next/link';
23

3-
import { getInfiniteScrollData } from '@/features/post/post-list/api/post-infinite-scroll';
4-
import { InfiniteScroll } from '@/features/post/post-list/ui/infinite-scroll';
5-
import { Button } from '@/shared/ui/button';
4+
import { getInfiniteScrollData } from '@/features/post/post-list';
5+
import { PostInfiniteScroll } from '@/features/post/post-list';
66

77
export default async function Home() {
88
const { data, nextCursor, hasMore } = await getInfiniteScrollData('', 10);
99

1010
return (
1111
<>
12-
<Button
13-
divClassName="text-right mr-8"
14-
buttonClassName="bg-black text-white font-semibold text-base p-2 rounded"
15-
>
12+
<Button asChild>
1613
<Link href="/posts/write">새 글 작성</Link>
1714
</Button>
1815

19-
<InfiniteScroll
16+
<PostInfiniteScroll
2017
postList={data}
2118
lastPostId={nextCursor}
2219
hasMore={hasMore}

apps/web/entities/post/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
export { createPost, getPostById, getPosts } from './api/post-api';
22
export { ERROR_MESSAGES } from './lib/error-messages';
33
export { validateFormField } from './lib/form-validation';
4-
export type { CreatePostDTO, Post, UpdatePostDTO } from './model/post-types';
4+
export type {
5+
CreatePostDTO,
6+
GetPostsCursor,
7+
Post,
8+
UpdatePostDTO,
9+
} from './model/post-types';
10+
export { mapPostToViewModel } from './model/post-view-model';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { Post } from '@/entities/post';
2+
import { formatToLocaleDate } from '@/shared/lib';
3+
4+
export const mapPostToViewModel = (post: Post) => ({
5+
...post,
6+
localeCreatedAt: formatToLocaleDate(post.createdAt),
7+
});
Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
1-
import { BASE_URL } from '@/shared/api/http-client';
2-
import { GetPostsCursor } from '@/shared/types/post-types';
3-
4-
const commonHeaders = new Headers();
5-
commonHeaders.append('Content-Type', 'application/json');
1+
import { GetPostsCursor } from '@/entities/post';
2+
import { httpClient } from '@/shared/api';
63

74
export async function getInfiniteScrollData(
85
cursor?: string,
96
limit?: number
107
): Promise<GetPostsCursor> {
11-
const followingURL = `/posts/infinite?${new URLSearchParams({
12-
...(cursor && { cursor }), // cursor가 있으면 추가, undefined or null이면 추가 안 함
13-
...(limit && { limit: limit.toString() }), // limit가 있으면 추가
14-
})}`;
15-
16-
const response = await fetch(`${BASE_URL}${followingURL}`, {
8+
return httpClient<GetPostsCursor>('/posts/infinite', {
179
method: 'GET',
18-
headers: commonHeaders,
10+
queryParams: {
11+
cursor,
12+
limit,
13+
},
1914
});
20-
21-
if (!response.ok) {
22-
throw new Error(`${response.status} ${response.statusText}`);
23-
}
24-
25-
return response.json();
2615
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { getInfiniteScrollData } from './api/post-infinite-scroll';
2+
export { PostInfiniteScroll } from './ui/post-infinite-scroll';
3+
export { PostItem } from './ui/post-item';

apps/web/features/post/post-list/ui/infinite-scroll.tsx renamed to apps/web/features/post/post-list/ui/post-infinite-scroll.tsx

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
'use client';
22

3-
import { useCallback, useEffect, useRef, useState } from 'react';
3+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
44

5-
import { getInfiniteScrollData } from '@/features/post/post-list/api/post-infinite-scroll';
6-
import { PostItem } from '@/features/post/post-list/ui/post-item';
7-
import { formatToLocaleDate } from '@/shared/lib/format-date';
8-
import { Post } from '@/shared/types/post-types';
5+
import { mapPostToViewModel } from '@/entities/post';
6+
import { Post } from '@/entities/post';
7+
import { getInfiniteScrollData } from '@/features/post/post-list';
8+
import { PostItem } from '@/features/post/post-list';
99

10-
type InfiniteScrollProps = {
10+
type PostInfiniteScrollProps = {
1111
postList: Post[];
1212
lastPostId: string | null;
1313
hasMore: boolean;
1414
};
1515

16-
export function InfiniteScroll({
16+
export function PostInfiniteScroll({
1717
postList,
1818
lastPostId,
1919
hasMore,
20-
}: InfiniteScrollProps) {
20+
}: PostInfiniteScrollProps) {
2121
const [loading, setLoading] = useState(false);
2222
const [posts, setPosts] = useState<Post[]>(postList);
2323
const [cursor, setCursor] = useState(lastPostId ? lastPostId : null);
@@ -55,24 +55,26 @@ export function InfiniteScroll({
5555
};
5656
}, [loadMorePosts]);
5757

58+
const postViewModels = useMemo(
59+
() => posts.map((post) => mapPostToViewModel(post)),
60+
[posts]
61+
);
62+
5863
return (
5964
<div>
60-
{posts.map(({ id, title, content, author, createdAt }) => {
61-
const localeCreatedAt = formatToLocaleDate(createdAt);
62-
return (
63-
<PostItem
64-
key={id}
65-
linkPostId={id}
66-
title={title}
67-
content={content}
68-
author={author}
69-
localeCreatedAt={localeCreatedAt}
70-
/>
71-
);
72-
})}
65+
{postViewModels.map((postViewModel) => (
66+
<PostItem
67+
key={postViewModel.id}
68+
linkPostId={postViewModel.id}
69+
title={postViewModel.title}
70+
content={postViewModel.content}
71+
author={postViewModel.author}
72+
localeCreatedAt={postViewModel.localeCreatedAt}
73+
/>
74+
))}
7375
<h3
7476
ref={target}
75-
className="mx-8 mb-4 mt-8 text-center text-9xl font-semibold"
77+
className="mx-8 mb-4 mt-8 text-center text-2xl font-semibold"
7678
>
7779
{posts.at(-1)?.id === cursor
7880
? '*************더 많은 게시글 로딩 중****************'

apps/web/features/post/post-list/ui/post-item.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function PostItem({
1313
<h3 className="pb-4 font-semibold">{title}</h3>
1414
<hr />
1515
<p className="pt-4">{content}</p>
16-
<br></br>
16+
<br />
1717
<div className="flex flex-col gap-2">
1818
<span className="rounded border border-gray-300 bg-gray-300 px-2 py-0.5 italic">
1919
{author}

apps/web/shared/api/http-client.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
export const BASE_URL = 'http://localhost:4000/api';
22

3+
interface HttpClientOptions extends RequestInit {
4+
queryParams?: Record<string, string | number | undefined>;
5+
}
6+
37
export const httpClient = async <T>(
48
endpoint: string,
5-
options?: RequestInit
9+
options?: HttpClientOptions
610
): Promise<T> => {
7-
const response = await fetch(`${BASE_URL}${endpoint}`, {
11+
const url = new URL(`${BASE_URL}${endpoint}`);
12+
13+
// queryParams가 있을 경우 URL에 추가
14+
if (options?.queryParams) {
15+
Object.entries(options.queryParams).forEach(([key, value]) => {
16+
if (value !== undefined) {
17+
url.searchParams.append(key, String(value));
18+
}
19+
});
20+
}
21+
22+
const response = await fetch(url.toString(), {
823
headers: {
924
'Content-Type': 'application/json',
1025
},

apps/web/shared/ui/button.tsx

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)