#typescript #next-js #tailwindcss

커뮤니티 페이지에 포스트 나열하기

Nov 20, 2022


포스트 나열하기

커뮤니티 상세 페이지 진입 시 해당 커뮤니티에 작성된 포스트들을 나열하기 위해 커뮤니티 상세 페이지 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);
        }
    }
...생략

커뮤니티 페이지 외 UI에서 포스트카드 컴포넌트 UI 분기

커뮤니티 페이지의 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>
                )}
...생략


*****

© 2021, Ritij Jain | Pudhina Fresh theme for Jekyll.