공감병동 프로젝트에서 내가 맡은 페이지들은 아래와 같다.
- 메인(Home) 페이지
- 글 작성 및 수정 페이지, 상세 게시글 페이지
- 진료과별 이야기 페이지
- 상급종합병원 목록 페이지
- 나의 기록 페이지
이번 포스트는 기록해놓고 싶은 기능에 대한 포스팅이다.
바로 상세 게시글 페이지의 응원 및 스크랩 기능인데,
유난히 기능 구현에 있어서 시행착오가 많았기 때문이다.
공감병동 프로젝트는 Next.js를 사용하였는데, 해당 학습은 github에 남겨져 있다.
먼제 Post Data를 데이터베이스에서 가져와야했다.
해당 페이지는 응원하기와 스크랩, 댓글 등 사용자 인터렉션이 발생하기 때문에
GetServerSideProps
를 이용하였다.
export const getServerSideProps: GetServerSideProps = async (context: any) => {
const { slug } = context.query;
const postRes = await axios.get(
`${process.env.NEXT_PUBLIC_API_URL}/post/detail/${encodeURI(slug)}`
);
const postData = postRes.data;
return { props: { postData } };
};
slug를 query에서 가져와 서버에 데이터를 요청하였고,
해당 데이터를 props로 페이지에 주입시켜준다.
postData에는 응원의 개수와 스크랩의 개수도 포함되어있다.
데이터베이스에서 불러온 응원 및 스크랩의 개수를 페이지 렌더링에 사용하게 되면
응원 및 스크랩을 누를때마다 모든 데이터를 다시 렌더링해주어야 하는 불편함이 있었다.
그래서 useState를 사용해 Count와 해당 토글을 눌렀는지의 여부를 상태로 관리하였다.
const [likes, setLikes] = useState({
isLike: false,
likesCount: postData.likes,
});
const [scraps, setScraps] = useState({
isScrap: false,
scrapsCount: postData.scraps,
});
이렇게 만들어둔 상태는 해당 토글을 누를때 마다 불린값과 카운트가 변화되도록 해주었다.
또한, 데이터베이스도 업데이트 되도록 함수를 만들었는데, submitPost
와 submitDelete
는 아래에서 따로 기록하겠다. (scraps도 동일한 로직이다.)
const handleLikes = async () => {
if (likes.isLike) {
setLikes({
isLike: false,
likesCount: likes.likesCount - 1,
});
submitDelete("deleteLike");
} else {
setLikes({
isLike: true,
likesCount: likes.likesCount + 1,
});
submitPost("postLike");
}
};
사용자가 이미 응원하기나 스크랩하기를 누른 상태인지를 확인하기 위해 useEffect를 사용하였다.
코드는 아래와 같이 작성되었는데, 사실 이 부분을 구현하기 위해 atom과 서버를 수정해 주었다.
useEffect(() => {
if (user.isLogin) {
if (user.likes) {
const findLikes = user.likes.filter(
(like: { posts_id: number }) => like.posts_id === postData.id
);
if (findLikes.length === 1) {
setLikes({
isLike: true,
likesCount: likes.likesCount,
});
}
}
if (user.scraps) {
const findScraps = user.scraps.filter(
(scrap: { posts_id: number }) => scrap.posts_id === postData.id
);
if (findScraps.length === 1) {
setScraps({
isScrap: true,
scrapsCount: scraps.scrapsCount,
});
}
}
}
}, []);
atom 수정
: user state에 likes와 scraps 배열 추가
export const userState = atom({ ..., default: { accessToken: "", isLogin: false, description: "", id: 0, img: "", loginType: "", nickname: "", social_id: "", likes: [{ posts_id: 0 }], scraps: [{ posts_id: 0 }], ... }, ..., });
서버 수정
: 로그인시 보내주는 user data에 likes와 scraps 배열 추가
await users.findOrCreate({ where: { social_id: userInfo.data.id }, defaults: userInfoValue, include: [ { model: likes, required: false, attributes: ["posts_id"], }, { model: scraps, required: false, attributes: ["posts_id"], }, ], });
이제 데이터를 불러와 상태로 저장하는 것을 완료했으니, 응원 및 스크랩 기능 구현만 남았다.
사실 응원 및 스크랩은 저장되는 데이터베이스 테이블만 다를 뿐 로직이 같기 때문에 변수로
likes
와 scraps
를 입력받아 하나의 함수로 구현해 주었다.
const submitPost = async (context: string) => {
const result = await axios.post(
`${process.env.NEXT_PUBLIC_API_URL}/post/${context}`,
{
users_id: user.id,
posts_id: postData.id,
},
{
headers: {
Authorization: `Bearer ${user.accessToken}`,
"Content-Type": "application/json",
},
withCredentials: true,
}
);
if (result.status === 201) {
if (context === "postLike") {
setUser({
...user,
likes: [...user.likes, { posts_id: postData.id }],
});
} else {
... //위와 동일
}
}
};
const submitDelete = async (context: string) => {
const result = await axios.delete(
`${process.env.NEXT_PUBLIC_API_URL}/post/${context}?users_id=${user.id}&posts_id=${postData.id}`,
{
headers: {
Authorization: `Bearer ${user.accessToken}`,
"Content-Type": "application/json",
},
withCredentials: true,
}
);
if (result.status === 200) {
if (context === "deleteLike") {
setUser({
...user,
likes: user.likes.filter(
(like: { posts_id: number }) => like.posts_id !== postData.id
),
});
} else {
... //위와 동일
}
}
};
사실 axios 요청은 앞선 다른 프로젝트들을 진행하며
충분히 익숙해졌기 때문에 문제없이 코드를 작성할 수 있었지만,
atom을 업데이트하는 것에 어려움을 겪었다.
많은 고민을 했는데, 막상 완성된 코드는 생각보다 단순하다..!
그냥 spread로 기존 user 데이터를 펼쳐주고 likes와 scraps를 필터링한 것 뿐..
그래도 오래 고민하고 다양한 시도를 해본 것이 뿌듯하게, 나름 만족스러운 결과가 나왔다!