문제 상황
즐겨 찾는 화장실 페이지에서 즐겨찾기 한 화장실의 리뷰 평점을 보여주는 작업을 진행했다.
supabase에서 컬럼을 외래키로 걸면 자동으로 인식하고 조인하듯이 2개의 테이블에서 조회할 수 있는데, 여기서 문제가 발생했다.
일단 평점을 가져오기 위해서는 총 3개의 테이블이 필요했다.
1. 화장실 정보 테이블 : toilet_location
2. 즐겨 찾는 화장실 테이블 : bookmark
3. 리뷰 목록 데이터 테이블 : review_info
supabase 공식 문서와 구글, 열심히 찾아다녔지만, 테이블 3개를 한 번에 조회하여 데이터를 가져오는 방법을 찾을 수 없었다.(내가 못 찾는 건가????/ 정말???)
튜터님께 도움을 요청해 enabled라는 옵션을 알게 됐다.
데이터 조회를 한 번의 요청으로 끝내고 싶었지만, 조인 이슈 때문에 결국 나는 2번의 요청을 보내게 된다.
해결 방법
1. 즐겨 찾는 화장실 목록 데이터는 toilet_location 테이블 + bookmark 테이블을 조인하여 데이터 조회
2. 리뷰 목록 데이터 : review_info 테이블에서 조회
이때 포인트는 2번-리뷰 목록 데이터를 조회할 때 1번-즐겨 찾는 화장실 데이터에서 id를 뽑아서 리뷰 테이블을 조회한다는 점이다.
마치 조인 비슷하게 기능하도록 로직을 작성했다.
방법은 enabled 옵션을 사용하는 것이다.
공식 문서에 dependent-queries 목록에 있다.
https://tanstack.com/query/latest/docs/framework/react/guides/dependent-queries
구현 로직
1. 즐겨 찾는 즐겨 찾는 화장실 목록 데이터는 toilet_location 테이블 + bookmark 테이블을 조인하여 즐겨 찾는 화장실 데이터 조회
2. 즐겨 찾는 화장실에 기반한 review_info 테이블 조회
먼저 즐겨 찾는 화장실 데이터를 조회한다.
select에는 원래 조회할 컬럼명이 들어가야 하지만, 외래키로 설정했을 경우 외래키로 설정된 테이블의 컬럼도 조회할 수 있다. 테이블명(컬럼명, 컬럼명 ...) 이렇게 조회하면 된다.
select에 toilet_location 테이블에서 toilet_name과 toilet_address를 조회했다.
//북마크 정보 조회 useQuery
const { data } = useQuery<Bookmark[]>({
queryFn: () => getData(USER_ID),
queryKey: [QUERY_KEY_BOOKMARK],
});
//유저의 북마크 목록 조회
export const getData = async (user_id: string): Promise<Bookmark[]> => {
try {
const { data, error } = await supabase
.from('bookmark')
.select('*, toilet_location(toilet_name, toilet_address)')
.eq('user_id', user_id);
if (error) {
console.error('Error fetching data:', error.message);
return [];
}
return data as Bookmark[];
} catch (error) {
console.error(error);
return [];
}
};
위와 같이 즐겨 찾는 화장실 데이터를 조회한 후 리뷰 데이터를 조회하기 위해서 id 만 따로 변수에 담는다.
const toiletIds = data?.map((bookmark) => bookmark.toilet_id) || []; //북마크한 화장실 데이터에서 toilet_id만 따로 담는다(리뷰 데이터 조회할 때 사용하기 위해서)
그러고 나서 화장실 id를 담은 변수를 리뷰 테이블을 조회할 때 값을 넘겨주고 조회하도록 했다.
이때, 즐겨 찾는 화장실 데이터를 조회한 후에 리뷰 데이터를 조회할 수 있도록 하기 위해서 enabled 옵션을 사용했다.
enabled: !!data
여기서 data(즐겨 찾는 화장실 데이터)가 조회돼서 값이 있을 때 리뷰 데이터 조회 요청을 실행한다.
//리뷰 데이터 useQuery
const { data: reviewData } = useQuery<ReviewRate[]>({
queryFn: () => getReviewData(toiletIds), //화장실 id 넘김
queryKey: [QUERY_KEY_REVIEW_RATE],
enabled: !!data,
});
toiletIds는 북마크가 여러 개일 수 있기 때문에. in으로 toiletIds에 해당하는 toilet_id 가 있는 데이터를 조회하도록 했다.
//리뷰 데이터 조회
export const getReviewData = async (toiletIds: number[]): Promise<ReviewRate[]> => {
try {
const { data, error } = await supabase
.from('review_info')
.select('toilet_id, toilet_loc_rate, toilet_clean_rate, toilet_pop_rate')
.in('toilet_id', toiletIds); //북마크 된 화장실 id
if (error) {
console.error('Error fetching data:', error.message);
return [];
}
return data as ReviewRate[];
} catch (error) {
console.error(error);
return [];
}
};
리뷰 평점 계산
calculateAverage를 좀 더 개선할 수 있을 것 같은데 하지 못했다.
일단 cnt로 수를 하나씩 더하는 부분과 avg 부분도... 개선하고 싶다.
//리뷰 평점 계산
export const calculateAverage = (toiletId: number, reviewData: ReviewRate[]) => {
let locRate = 0;
let cleanRate = 0;
let popRate = 0;
let cnt = 0;
reviewData?.forEach((el) => {
if (el.toilet_id === toiletId) {
locRate += el.toilet_loc_rate;
cleanRate += el.toilet_clean_rate;
popRate += el.toilet_pop_rate;
cnt += 1;
}
});
let avg = locRate / cnt + cleanRate / cnt + popRate / cnt / 3;
return getStarRating(avg ? avg : 1);
};
export const getStarRating = (result: number): string => {
switch (true) {
case result === 5:
return `⭐⭐⭐⭐⭐`;
case result > 4:
return `⭐⭐⭐⭐`;
case result > 3:
return `⭐⭐⭐`;
case result > 2:
return `⭐⭐`;
default:
return `⭐`;
}
};
느낀점
일단 근본적인 해결이 아니라 아쉽지만, enabled와 데이터 조회 요청을 2번하여 원하는 결과는 얻을 수 있었다.
이후에도 같은 상황이 생길 것 같아서 더 찾아봐야할 것 같다.
enabled라는 유용한 옵션을 알게돼서 이후에도 많이 사용할 수 있을 것 같다.
그리고 불필요한 요청도 방지할 수 있을 것 같다.
[결과 화면]