팀프로젝트를 하면서 늘 고민하는 부분이 컴포넌트 재사용이었다. 꼭 해야지 해야지 하면서 늘 시간이 부족해서 못하거나 전문 디자이너 없이 각자 취향에 맞는? 그런 css 디자인을 해서 (쓰고 보니 말이 안되긴 함ㅎ) 버튼 정도 밖에 써보지 못했던 것 같다. 아니면 색상정도?
이번 최종 팀프로젝트를 하면서 레시피카드가 똑같이 사용되고 있었고 이번에는 꼭 하나의 컴포넌트를 만들어서 재사용해보고자 했다.
컴포넌트 재사용하기
첫번째 사진은 메인페이지, 두번째 사진은 스크랩 페이지의 레시피 카드 컴포넌트이다. 처음에는 각자 구현하고 있었는데 어차피 똑같은 부분이라면 모달이나 버튼처럼 하나 만들어서 재사용하고 싶었고 메인페이지 작업하는 팀원한테 말하고 적용해보았다.
"use client";
import { getUserId, getUserNickname } from "@/serverActions/profileAction";
import Image from "next/image";
import React, { useEffect, useState } from "react";
import FireFilledIcon from "@images/fireFilled.svg";
import FireEmptyIcon from "@images/fireEmpty.svg";
import browserClient from "@/supabase/client";
import { RecipeCardProps } from "@/types/main";
import { useRouter } from "next/navigation";
import LikeButton from "../common/button/LikeButton";
import ScrapButton from "../common/button/ScrapButton";
import TrashCanIcon from "@images/trashcan.svg";
const RecipeCard = ({ post }: ExtendedRecipeCardProps) => {
const [nickname, setNickname] = useState("");
const router = useRouter();
useEffect(() => {
const fetchUserProfile = async () => {
if (post.user_id) {
const userProfile = await getUserNickname(post.user_id);
setNickname(userProfile);
}
};
const fetchIsUserLiked = async () => {
const userId = await getUserId();
const { error } = await browserClient
.from("LIKE_TABLE")
.select("*")
.eq("user_id", userId)
.eq("post_id", post.post_id);
if (error) {
throw error;
}
};
fetchUserProfile();
fetchIsUserLiked();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className="flex flex-col gap-2">
<div className="relative h-[12rem] w-[12rem]">
{post.recipe_img_done && (
<Image
src={post.recipe_img_done}
alt="레시피 사진"
fill
className="cursor-pointer object-cover"
onClick={() => router.push(`/myrecipedetail/${post.post_id}`)}
/>
)}
</div>
<p>{post.recipe_title}</p>
<p className="text-gray-500">{nickname}</p>
<div className="flex justify-between">
<div className="flex">
<Image src={FireFilledIcon} alt="레시피 난이도" />
<Image src={post.recipe_level !== "하" ? FireFilledIcon : FireEmptyIcon} alt="레시피 난이도" />
<Image src={post.recipe_level === "상" ? FireFilledIcon : FireEmptyIcon} alt="레시피 난이도" />
</div>
<div className="flex gap-2">
<LikeButton postId={post.post_id} />
<ScrapButton postId={post.post_id} />
</div>
</div>
</div>
);
};
export default RecipeCard;
메인페이지에 적용되어있던 컴포넌트는 위와 같았다.
내가 적용할 스크랩 페이지와 달랐던 부분은 2가지였다.
postId
가 필요하다.어떻게 구현할 수 있을지 구글링 한 후 내가 적용할 부분을 prop
으로 전달해주면 되었다.
interface ExtendedRecipeCardProps extends RecipeCardProps {
isEditMode?: boolean;
onDelete?: (postId: string) => void;
}
메인페이지에서 사용하고 있던 타입스크립트에 extends
를 사용하여 isEditMode
상태변화와 onDelete
함수를 추가해주었다.
? 옵셔널
을 준 이유는 메인페이지 안에서도 건강카테고리, 인기레시피 등으로 재사용하고 있었기 때문에 이 값이 반드시!! 들어있지 않아도 에러가 발생하지 않게 만들었다.
{isEditMode ? (
// 편집모드일 때 -> 휴지통 아이콘 표시
onDelete && (
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onDelete(post.post_id);
}}
className="text-gray-500 hover:text-gray-700"
>
<Image src={TrashCanIcon} alt="삭제 아이콘" width={22} height={22} />
</button>
)
) : (
// 편집 모드가 아닐 때 -> LikeButton / ScrapButton 표시
<div className="flex items-center gap-2">
<LikeButton postId={post.post_id} />
<ScrapButton postId={post.post_id} />
</div>
)}
isEditMode
값이 true
가 되면 좋아요, 스크랩 버튼이 보여지게 하고
편집 버튼을 누르면 삭제아이콘이 활성화되고 다시 눌러서 false
값으로 변하면서 사라지게 구현했다.
걱정했던 것보다 비교적 간단하게 구현한 것 같은데 조금 고민되는 부분이 있다.
매번 이렇게 prop으로 넘겨주게 된다면? 제일 기본이 되는 컴포넌트는 나중에 엄청난 prop을 받고 로직도 길어지지 않을까? 하는 생각이 들었다.
다른 방법은 없는지 조금 더 찾아보고 개선할 수 있다면 그 방법으로도 적용해봐야겠다.