
링크에 어떤 값을 넣어도 게시글 작성 및 수정이 되는 것이 문제였다. 잘못된 값을 넣을 경우 오류가 나므로, 페이지를 이용하는데에 큰 결함이 있을 것으로 생각했다. 때문에 링크에 관련한 url 유효성 검사를 추가하여 문제를 해결하였다.
const urlRegex = /^(https?|ftp):\/\/(-\.)?([^\s\/?\.#-]+\.?)+(\/[^\s]*)?$/i;
if (link && !urlRegex.test(link)) {
alert("링크 형식이 잘못되었습니다.");
return;
}
관련 링크는 선택이기 때문에 link가 있을 경우에만 유효성 검사를 하는 것으로 했고, urlRegex는 구글링을 통해 링크 형식을 검사하는 코드를 가지고 왔다. 만약 제대로 된 링크 형식이 아닐 경우 alert 후 바로 return을 하도록 했다.
공연 상세 페이지에서 좋아요 버튼을 클릭한 다음, 공연 정보 페이지(리스트 페이지)로 이동하면 좋아요 숫자가 바로 적용이 되지 않는 문제가 있었다. 캐싱 때문에 그런 것 같았으나 캐싱을 없애고 싶지는 않았기에, 어찌 보면 간단한 방법으로 좋아요 버튼을 클릭하면 바로 invalidateQueries를 통해 적용을 바로 시켜주는 로직을 사용했다. tanstack query의 장점이 바로 이런 곳에서 나오는 게 아닐까 하는 생각이 들었다.
아래는 좋아요 버튼을 클릭할 경우의 전체 함수이다.
const onClickLikeHandler = async () => {
if (!user) {
alert("로그인한 사용자만 가능합니다.");
return;
}
const newLike = {
like_id: uuidv4(),
post_id: postId,
user_id: user.id,
};
const { data } = await supabase
.from("concert_likes")
.select()
.eq("post_id", postId)
.eq("user_id", user.id);
if (data && data.length) {
await supabase
.from("concert_likes")
.delete()
.eq("post_id", postId)
.eq("user_id", user.id);
setHeart(false);
setLike(like - 1);
} else {
await supabase.from("concert_likes").insert(newLike).select();
setHeart(true);
setLike(like + 1);
}
// concerts 갱신
queryClient.invalidateQueries({
queryKey: ["concerts"],
});
};
바로 갱신을 하여 뒤(공연 정보 페이지)로 가더라도 바로 적용된 것을 볼 수 있었다.
공연 정보 페이지에는 공연 리스트를 최신순, 랭킹순, 공연종료임박순으로 정렬을 할 수 있는 기능이 있다. 그러나 처음 들어갔을 때의 페이지인 최신순 페이지를 제외하고는, 무한 스크롤 기능이 작동하지 않았다.
게다가 현재 가져온 6개의 리스트 내에서만 랭킹순, 공연종료임박순 정렬을 했기에, 만약 그 뒤의 리스트가 1위라 하더라도 가져오지 못하는 현상이 발생했다. 때문에 대대적인 리팩토링이 필요하다고 느꼈다.
기존의 방식은 가져온 데이터를 똑같이 공유하면서 해당 데이터 내에서 정렬을 해주는 방식을 사용했지만, 그 방법이 이제 통하지 않으므로 정렬을 할 때마다 새롭게 supabase에서 가져오는 방식을 택했다. activeSort 상태관리를 통해 해당 상태를 인자로 넣어줌으로 getConcerts 함수를 구현했다.
// getConcerts.ts
import supabase from "./supabase/client";
export default async function getConcerts(page = 0, sort = "latest") {
const limit = 6
let query = supabase.from('concert_posts').select(`*, concert_likes(post_id)`, { count: 'exact' });
if (sort === "latest"){
query = query.order('created_at', { ascending: false })
} else if (sort === "imminent") {
query = query.order('end_date', {ascending:true})
}
const {data:posts, error: postError, count} = await query
if (postError) {
throw new Error(postError.message);
}
let sortedPosts = posts;
if (sort === "ranking"){
sortedPosts = posts.sort((a,b)=>b.concert_likes.length - a.concert_likes.length)
}
const start = page * limit;
const end = start + limit;
const paginatedPosts = sortedPosts.slice(start, end);
if (count){
const nextCursor = end < count ? page + 1 : null;
return { posts: paginatedPosts, nextCursor };
}
return { posts: paginatedPosts, nextCursor: null };
}
전체 데이터를 가져온 다음 받아온 sort 인자에 따라 정렬을 해준다. 이 때 ranking의 경우는 order를 통해 가져올 수 없기 때문에 (배열의 length로 비교를 해줘야하는 조금 특이한 방식이기 때문에) 가져온 데이터에 따로 sort를 진행해주는 것으로 했다. 그 다음 가져온 posts를 잘라서 return해준다. 다른 부분은 기존의 방식과 같았다.
이렇게 하니, 굳이 리스트 파일에서 sortedConcerts같이 정렬된 리스트 상태를 따로 관리해줄 필요가 없었다. 클릭 할 때마다 매번 activeSort 상태만 업데이트해주면 됐다.
// sort 기능
// 최신순 정렬
const latestSort = () => {
setActiveSort("latest");
refetch();
};
// 랭킹순 정렬
const rankingSort = () => {
setActiveSort("ranking");
refetch();
};
// 공연 종료 임박순 정렬
const imminentSort = () => {
setActiveSort("imminent");
refetch();
};
기존의 함수보다 훨씬 간단해졌다. 그저 activeSort 상태를 갱신하고 refetch를 해주면 해결됐다.
이런 식으로 코드를 리팩토링 하니 각 정렬에 따른 리스트들도 잘 가져올 수 있었고, 무한 스크롤 기능도 다른 정렬에서도 제대로 작동할 수 있었다.