본문 바로가기

카테고리 없음

Fiesta Tour_12일차

채팅 목록을 게시글 정보가아닌 메시지를 보낸 유저 정보로 고정하기

기존 로직은 게시글 정보가 보였고 , 단순하게 조건문만 추가해서는 마지막으로 채팅을 보낸 사람의 정보가 나오면서 채팅 목록에 내 계정 정보가 나오는 문제 발생 -> 해결을 위해 조건문 추가 및 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 });

쿼리 설명

  1. .from('messages'): messages 테이블을 기준으로 쿼리를 실행합니다.
  2. .select(...): 선택할 컬럼을 명시합니다. 여기서는 messages 테이블의 모든 컬럼과 조인된 users 테이블에서 발신자와 수신자의 id, name, avatar를 선택합니다.
  3. .eq('post_id', postId): post_id가 주어진 postId와 일치하는 메시지를 필터링합니다.
  4. .or(...): 주어진 조건 중 하나라도 만족하는 경우의 메시지를 필터링합니다. 여기서는 두 개의 조건을 OR 연산으로 결합합니다.
  5. .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라는 별칭으로 접근할 수 있습니다.