juni
WelKo_채팅 로직 개선 본문
채팅 목록을 게시글 정보가아닌 메시지를 보낸 유저 정보로 고정하기
기존 로직은 게시글 정보가 보였고 , 단순하게 조건문만 추가해서는 마지막으로 채팅을 보낸 사람의 정보가 나오면서 채팅 목록에 내 계정 정보가 나오는 문제 발생 -> 해결을 위해 조건문 추가 및 userId를 받아오는 과정에서도 로직을 추가하여 구현
하지만 채팅 목록이 많아짐에따라 userId는 채팅 목록 갯수만큼 ( 현재는 4번 ) , senderDetails는 4x4 총 16번 호출 되는 비효율 발생 -> 추후 개선 필요
'use client';
import React from 'react';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';
import Image from 'next/image';
import { API_MYPAGE_CHATS, API_POST_DETAILS, API_MYPAGE_PROFILE } from '@/utils/apiConstants';
import axios from 'axios';
type ChatListProps = {
userId: string;
};
type Chat = {
post_id: string;
sender_id: string;
receiver_id: string;
content: string;
created_at: string;
};
type Post = {
id: string;
title: string;
image: string;
};
type User = {
id: string;
name: string;
avatar: string;
};
const ChatList = ({ userId }: ChatListProps) => {
const router = useRouter();
const fetchPostDetails = async (postId: string): Promise<Post> => {
const response = await axios.get(API_POST_DETAILS(postId));
return response.data;
};
const fetchUserDetails = async (userId: string): Promise<User> => {
const response = await axios.get(API_MYPAGE_PROFILE(userId));
return response.data;
};
const {
data: chatData,
error: chatError,
isPending: chatPending
} = useQuery<Chat[]>({
queryKey: ['chatList', userId],
queryFn: async () => {
const response = await axios.get(API_MYPAGE_CHATS(userId));
return response.data;
}
});
const postIds = chatData?.map((chat) => chat.post_id) || [];
const userIds = chatData ? Array.from(new Set(chatData.flatMap((chat) => [chat.sender_id, chat.receiver_id]))) : [];
const {
data: postData,
error: postError,
isPending: postPending
} = useQuery<Post[]>({
queryKey: ['postDetails', postIds],
queryFn: async () => {
const postDetails = await Promise.all(postIds.map((postId) => fetchPostDetails(postId)));
return postDetails;
},
enabled: postIds.length > 0
});
const {
data: userData,
error: userError,
isPending: userPending
} = useQuery<User[]>({
queryKey: ['userDetails', userIds],
queryFn: async () => {
const userDetails = await Promise.all(userIds.map((id) => fetchUserDetails(id)));
return userDetails;
},
enabled: userIds.length > 0
});
if (chatPending || postPending || userPending) return <div>Loading...</div>;
if (chatError || postError || userError) return <div>Error loading data</div>;
return (
<div>
{chatData?.map((chat: Chat, index: number) => {
const postDetails = postData?.find((post) => post.id === chat.post_id);
const receiverId = userId === chat.sender_id ? chat.receiver_id : chat.sender_id;
const senderDetails = userData?.find((user) => user.id === receiverId);
return (
<div
key={index}
onClick={() =>
router.push(
`/${userId}/${receiverId}/chatpage?postId=${chat.post_id}&postTitle=${postDetails?.title}&postImage=${postDetails?.image}`
)
}
>
{senderDetails && (
<div className="mb-4 flex items-center">
<Image
className="mr-4"
src={senderDetails.avatar || '/icons/upload.png'}
alt={senderDetails.name || 'Default name'}
width={40}
height={40}
/>
<p>{senderDetails.name}</p>
</div>
)}
<p>{chat.content}</p>
<p>{new Date(chat.created_at).toLocaleString()}</p>
</div>
);
})}
</div>
);
};
export default ChatList;
이 코드는 chatData가 존재하는 경우 sender_id와 receiver_id를 포함하는 중복 제거된 사용자 ID 배열을 생성합니다. chatData가 존재하지 않는 경우 빈 배열을 반환합니다.
const userIds = chatData ? Array.from(new Set(chatData.flatMap((chat) => [chat.sender_id, chat.receiver_id]))) : [];
단계별 설명
chatData?.flatMap(chat => [chat.sender_id, chat.receiver_id]):
- flatMap은 map 메서드와 flat 메서드를 결합한 것입니다. 각 요소에 대해 콜백 함수를 실행한 결과를 하나의 배열로 평탄화합니다.
- 여기서 각 chat 객체에 대해 [chat.sender_id, chat.receiver_id] 배열을 반환합니다.
const chatData = [ { sender_id: 'user1', receiver_id: 'user2', content: 'Hello' }, { sender_id: 'user3', receiver_id: 'user1', content: 'Hi' } ];
const result = chatData.flatMap(chat => [chat.sender_id, chat.receiver_id]);
console.log(result); // 결과: ['user1', 'user2', 'user3', 'user1']
new Set([...]):
- Set 객체는 중복된 값을 허용하지 않는 데이터 구조입니다.
- Set 객체에 배열을 전달하여 중복된 값을 제거합니다.
const userIds = chatData ? Array.from(new Set(chatData.flatMap(chat => [chat.sender_id, chat.receiver_id]))) : []; console.log(userIds);
// 결과: ['user1', 'user2', 'user3']
Array.from(new Set([...])):
- Array.from은 Set 객체를 다시 배열로 변환합니다.
const uniqueUserArray = Array.from(new Set(['user1', 'user2', 'user3', 'user1']));
console.log(uniqueUserArray); // 결과: ['user1', 'user2', 'user3']
전체 코드:
- chatData가 존재하면 flatMap을 사용하여 각 chat 객체에서 sender_id와 receiver_id를 포함하는 배열을 생성하고, Set을 사용하여 중복을 제거한 후 Array.from을 사용하여 배열로 변환합니다.
- chatData가 존재하지 않으면 빈 배열을 반환합니다.
const uniqueUsers = new Set(['user1', 'user2', 'user3', 'user1']); console.log(uniqueUsers);
// 결과: Set { 'user1', 'user2', 'user3' }
코드 전체 예제
const chatData = [ { sender_id: 'user1', receiver_id: 'user2', content: 'Hello' }, { sender_id: 'user3', receiver_id: 'user1', content: 'Hi' } ];
const userIds = chatData ? Array.from(new Set(chatData.flatMap(chat => [chat.sender_id, chat.receiver_id]))) : []; console.log(userIds);
// 결과: ['user1', 'user2', 'user3']
이 예제는 chatData가 있을 때 userIds 배열을 생성하는 과정을 보여줍니다. 각 chat 객체에서 sender_id와 receiver_id를 추출하여 중복을 제거하고 배열로 변환합니다.
-> 이렇게 하면 채팅에 관련된 모든 고유한 사용자 ID를 userIds 배열에 담을 수 있습니다.
const { data, error } = await supabase
.from('messages')
.select(
`
*,
sender:users!messages_sender_id_fkey ( id, name, avatar ),
receiver:users!messages_receiver_id_fkey ( id, name, avatar )
`
)
.eq('post_id', postId)
.or(
`and(sender_id.eq.${senderId},receiver_id.eq.${receiverId}),and(sender_id.eq.${receiverId},receiver_id.eq.${senderId})`
)
.order('created_at', { ascending: true });
쿼리 설명
- .from('messages'): messages 테이블을 기준으로 쿼리를 실행합니다.
- .select(...): 선택할 컬럼을 명시합니다. 여기서는 messages 테이블의 모든 컬럼과 조인된 users 테이블에서 발신자와 수신자의 id, name, avatar를 선택합니다.
- .eq('post_id', postId): post_id가 주어진 postId와 일치하는 메시지를 필터링합니다.
- .or(...): 주어진 조건 중 하나라도 만족하는 경우의 메시지를 필터링합니다. 여기서는 두 개의 조건을 OR 연산으로 결합합니다.
- .order('created_at', { ascending: true }): created_at 컬럼을 기준으로 메시지를 오름차순으로 정렬합니다.
쿼리 상세 설명
테이블 및 외래 키
- messages 테이블: 각 메시지에는 발신자(sender_id)와 수신자(receiver_id)가 있습니다.
- users 테이블: 발신자와 수신자의 정보를 저장합니다.
- 외래 키: messages 테이블의 sender_id와 receiver_id는 각각 users 테이블의 id를 참조합니다.
조인
- sender!messages_sender_id_fkey: messages 테이블의 sender_id와 users 테이블의 id를 조인하여 발신자 정보를 가져옵니다.
- receiver!messages_receiver_id_fkey: messages 테이블의 receiver_id와 users 테이블의 id를 조인하여 수신자 정보를 가져옵니다.
- sender!messages_sender_id_fkey ( id, name, avatar )
- messages 테이블의 sender_id 외래 키와 users 테이블의 id 컬럼을 조인하여, users 테이블의 id, name, avatar 컬럼을 가져옵니다.
- 조인된 users 테이블의 데이터는 sender라는 별칭으로 접근할 수 있습니다.
- receiver!messages_receiver_id_fkey ( id, name, avatar )
- messages 테이블의 receiver_id 외래 키와 users 테이블의 id 컬럼을 조인하여, users 테이블의 id, name, avatar 컬럼을 가져옵니다.
- 조인된 users 테이블의 데이터는 receiver라는 별칭으로 접근할 수 있습니다.
'프로젝트 > WelKo' 카테고리의 다른 글
WelKo_찜 카테고리 (0) | 2024.08.02 |
---|---|
WelKo_채팅 목록 및 예약자 명단 (0) | 2024.08.01 |
WelKo_채팅 UI 및 로직 개선 (0) | 2024.07.29 |
WelKo_상세페이지에서 메시지 보내기 (0) | 2024.07.28 |
WelKo_ReactQuery 카테고리 분류 (0) | 2024.07.26 |