커뮤니티 상세 페이지 진입 시 해당 커뮤니티에 작성된 포스트들을 나열하기 위해
커뮤니티 상세 페이지 reddit-clone-app\client\src\pages\r\[sub].tsx
파일에 아래 내용을 추가한다.
...생략
import PostCard from '../../components/PostCard';
...생략
let renderPosts;
if(!sub) {
// 커뮤니티 존재하지 않을 때
renderPosts = <p className='text-lg text-center'>로딩중...</p>
} else if(sub.posts.length === 0) {
// 포스트가 없을 때
renderPosts = <p className='text-lg text-center'>아직 작성된 포스트가 없습니다.</p>
} else {
renderPosts = sub.posts.map((post: Post) => (
<PostCard key={post.identifier} post={post} />
))
}
...생략
{/* 포스트와 사이드바 */}
<div className='flex max-w-5xl px-4 pt-5 mx-auto'>
// *****
// 추가 부분
<div className='w-full md:mr-3 md:w-8/12'>{renderPosts}</div>
<Sidebar sub={sub} />
</div>
...생략
이후 renderPosts
변수에서 포스트를 나열하기 위해 추가되는 PostCard
컴포넌트 파일을 추가한다.
reddit-clone-app\client\src\components\PostCard.tsx
파일을 생성하고 아래 내용을 입력한다.
import classNames from 'classnames'
import Link from 'next/link'
import Image from 'next/image'
import React from 'react'
import { Post } from '../types'
import dayjs from 'dayjs'
interface PostCardProps {
post: Post
}
const PostCard = ({ post: {
identifier, slug, title, body, subName, createdAt, voteScore, userVote, commentCount, url, username, sub
} }: PostCardProps) => {
return (
<div
className='flex mb-4 bg-white rounded'
id={identifier}
>
{/* 투표 기능 부분 */}
<div className="flex-shrink-0 w-10 py-2 text-center rounded-l">
{/* 좋아요 부분 */}
<div
className="w-6 mx-auto text-gray-400 rounded cursor-pointer hover:bg-gray-300 hover:text-red-500"
// onClick={() => vote(1)}
>
<i
className={classNames("fas fa-arrow-up", {
"text-red-500": userVote === 1
})}
>
</i>
</div>
<p className="text-xs font-bold">{voteScore}</p>
{/* 싫어요 부분 */}
<div
className="w-6 mx-auto text-gray-400 rounded cursor-pointer hover:bg-gray-300 hover:text-blue-500"
// onClick={() => vote(-1)}
>
<i
className={classNames("fas fa-arrow-down", {
"text-blue-500": userVote === -1
})}
>
</i>
</div>
</div>
{/* 포스트 데이터 부분 */}
<div className='w-full p-2'>
<p className='text-xs test-gray-400'>
Posted by
<Link href={`/r/${username}`}>
<a className='mx-1 hover:underline'>/r/{username}</a>
</Link>
<Link href={url}>
<a className='mx-1 hover:underline'>
{dayjs(createdAt).format('YYYY-MM-DD HH:mm')}
</a>
</Link>
</p>
<Link href={url}>
<a className='my-1 text-lg font-medium'>{title}</a>
</Link>
{body && <p className='my-1 text-sm'>{body}</p>}
<div className='flex'>
<Link href={url}>
<a>
<i className='mr-1 fas fa-comment-alt fa-xs'></i>
<span>{commentCount}</span>
</a>
</Link>
</div>
</div>
</div>
)
}
export default PostCard
커뮤니티 페이지 내 포스트 카드 컴포넌트인 reddit-clone-app\client\src\components\PostCard.tsx
파일에
아래 vote
함수를 추가한다.
...생략
// 사용자 인증정보 가져오기
const {authenticated} = useAuthState();
const vote = async (value: number) => {
// 사용자 로그인 세션 정상여부 확인
if(!authenticated) router.push("/login");
// 포스트에 대한 현재 사용자 투표 값과 새로 입력된
if(value === userVote) value = 0;
try {
// 사전에 생성해둔 votes API 요청
await axios.post("/votes", { identifier, slug, value });
} catch (error) {
console.log(error);
}
}
return (
<div
className='flex mb-4 bg-white rounded'
id={identifier}
>
{/* 투표 기능 부분 */}
<div className="flex-shrink-0 w-10 py-2 text-center rounded-l">
{/* 좋아요 부분 */}
<div
className="w-6 mx-auto text-gray-400 rounded cursor-pointer hover:bg-gray-300 hover:text-red-500"
// 좋아요 투표 함수 실행 부분
onClick={() => vote(1)}
>
<i
className={classNames("fas fa-arrow-up", {
"text-red-500": userVote === 1
})}
>
</i>
</div>
<p className="text-xs font-bold">{voteScore}</p>
{/* 싫어요 부분 */}
<div
className="w-6 mx-auto text-gray-400 rounded cursor-pointer hover:bg-gray-300 hover:text-blue-500"
// 싫어요 투표 함수 실행 부분
onClick={() => vote(-1)}
>
<i
className={classNames("fas fa-arrow-down", {
"text-blue-500": userVote === -1
})}
>
</i>
</div>
</div>
...생략
포스트 카드 투표 이후 즉시 화면 갱신을 위해 커뮤니티-하위 포스트
데이터를 가져오는 useSWR
구문에 mutate
를 추가한다.
reddit-clone-app\client\src\pages\r\[sub].tsx
파일을 아래와 같이 수정.
...생략
// mutate를 추가한다.
const {data: sub, error, mutate} = useSWR(subName ? `/subs/${subName}` : null);
...생략
if(!sub) {
renderPosts = <p className='text-lg text-center'>로딩중...</p>
} else if(sub.posts.length === 0) {
renderPosts = <p className='text-lg text-center'>아직 작성된 포스트가 없습니다.</p>
} else {
renderPosts = sub.posts.map((post: Post) => (
// subMutate Props 추가
// 포스트 카드 컴포넌트 props로 mutate를 전달
<PostCard key={post.identifier} post={post} subMutate={mutate}/>
))
}
...생략
투표 이후 커뮤니티-하위 포스트
데이터를 다시 가져와 화면을 갱신해야 하므로 앞서 추가한 포스트 카드 vote
함수에
아래 내용을 추가한다.
...생략
const PostCard = ({ post:
{
identifier,
slug,
title,
body,
subName,
createdAt,
voteScore,
userVote,
commentCount,
url,
username,
sub
},
// subMutate Props를 포스트 카드 컴포넌트에서 받아 사용
subMutate }: PostCardProps) => {
const router = useRouter();
const isInSubPage = router.pathname === '/r/[sub]';
const {authenticated} = useAuthState();
const vote = async (value: number) => {
if(!authenticated) router.push("/login");
if(value === userVote) value = 0;
try {
await axios.post("/votes", { identifier, slug, value });
// 추가된 mutate
subMutate();
} catch (error) {
console.log(error);
}
}
...생략
커뮤니티 페이지의 context path
는 /r/[sub]
형태의 path를 가진다.
커뮤니티 페이지가 아닌 곳에서 추후 포스트 카드 컴포넌트를 사용할 예정이므로
해당 패스가 아닌 페이지 UI에서의 PostCard.tsx
컴포넌트 UI를 출력하는 분기를 아래와 같이 추가한다.
reddit-clone-app\client\src\components\PostCard.tsx
파일에 아래 내용을 추가한다.
...생략
const isInSubPage = router.pathname === '/r/[sub]';
...생략
{/* 포스트 데이터 부분 */}
<div className='w-full p-2'>
{/* 커뮤니티 페이지가 아닐 때 = path가 "/r/[sub]"가 아닐 때 */}
{!isInSubPage && (
<div className='flex items-center'>
<Link href={`/r/${subName}`}>
<a>
<Image
src={sub!.imageUrl}
alt="sub"
className='rounded-full cursor-pointer'
width={12}
height={12}
/>
</a>
</Link>
<Link href={`/r/${subName}`}>
<a className='ml-2 text-xs font-bold cursor-pointer hover:underline'>
/r/{subName}
</a>
</Link>
<span className='mx-1 text-xs text-gray-400'>ㆍ</span>
</div>
)}
...생략