본문 바로가기
프로젝트/chat-app

채팅방 리스트 목록 구현하기

by 진!!!!! 2024. 12. 8.

채팅방 디자인을 어떻게 하는게 좋을까?

디스코드 같은 디자인으로, 

왼쪽에 채팅방 리스트, 오른쪽에 채팅 리스트를 넣고 디스코드처럼 채팅방 리스트 최상단에 채팅방 추가 버튼을 넣는 게 좋을 것 같다.

 

채팅방 리스트 목록 디자인하기

 

shadcn/ui를 참고해서,

왼쪽에는 저런 리스트 형태로 채팅방 리스트가 있고 오른쪽에는 채팅창 화면이 보이게 할 예정이다.

 

더미 데이터를 만들었다.

const chatRooms = [
    {
      id: 1,
      name: "일반 채팅방",
      participants: 15,
      lastMessage: "안녕하세요!",
      lastActivityTime: "방금 전",
      unreadCount: 3,
      lastSender: {
        name: "김철수",
        avatar: "./../public/image/profile.png",
      },
    },
    {
      id: 2,
      name: "개발자 모임",
      participants: 8,
      lastMessage: "리액트 너무 재밌어요",
      lastActivityTime: "5분 전",
      unreadCount: 0,
      lastSender: {
        name: "이영희",
        avatar: "",
      },
    },
    {
      id: 3,
      name: "취미 공유방",
      participants: 23,
      lastMessage: "저도 같이 하고 싶어요",
      lastActivityTime: "30분 전",
      isPrivate: false,
      unreadCount: 5,
      lastSender: {
        name: "박지민",
        avatar: "",
      },
    },
  ];

 

그리고 shadcn/ui의 Avatar을 이용해서 임시로 프로필 사진과 프로필 사진이 없을 경우 닉네임의 첫글자가 보이게 했다.

 

import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";

const ChatRoomList = () => {
  const chatRooms = [
    {
      id: 1,
      name: "일반 채팅방",
      participants: 15,
      lastMessage: "안녕하세요!",
      lastActivityTime: "방금 전",
      unreadCount: 3,
      lastSender: {
        name: "김철수",
        avatar: "./../public/image/profile.png",
      },
    },
    {
      id: 2,
      name: "개발자 모임",
      participants: 8,
      lastMessage: "리액트 너무 재밌어요",
      lastActivityTime: "5분 전",
      unreadCount: 0,
      lastSender: {
        name: "이영희",
        avatar: "",
      },
    },
    {
      id: 3,
      name: "취미 공유방",
      participants: 23,
      lastMessage: "저도 같이 하고 싶어요",
      lastActivityTime: "30분 전",
      unreadCount: 5,
      lastSender: {
        name: "박지민",
        avatar: "",
      },
    },
  ];

  return (
    <div className="p-4">
      {chatRooms.map((chatRoom) => (
        <div
          key={chatRoom.id}
          className="flex items-center justify-between p-5 border-b hover:bg-slate-50 cursor-pointer"
        >
          <div className="flex items-center gap-4 flex-1">
            <Avatar>
              <AvatarImage src={chatRoom.lastSender.avatar} />
              <AvatarFallback>{chatRoom.lastSender.name[0]}</AvatarFallback>
            </Avatar>

            <div className="flex flex-col gap-1 flex-1">
              <div className="flex items-center gap-2">
                <span className="font-medium">{chatRoom.name}</span>
                <span className="font-light text-xs text-gray-500">
                  {chatRoom.participants}명
                </span>
              </div>
              <div className="flex items-center gap-1 text-sm text-gray-500">
                <span className="line-clamp-1 max-w-[200px]">
                  {chatRoom.lastMessage}
                </span>
              </div>
            </div>
          </div>
          <div className="flex flex-col items-end gap-2 min-w-[80px]">
            <span className="text-xs text-gray-500">
              {chatRoom.lastActivityTime}
            </span>
            {chatRoom.unreadCount > 0 && (
              <Badge variant="destructive" className="rounded-full">
                {chatRoom.unreadCount}
              </Badge>
            )}
          </div>
        </div>
      ))}
    </div>
  );
};

export default ChatRoomList;

 

디자인은 다 끝났다!

이제 api를 연결해보자.

 

axios로 api 연동하기

axios 설치하기

npm install axios

axios란 무엇인가요?

api를 연동할 수 있는 방법은 fetch를 이용하는 방법과 axios를 이용하는 방법 2가지가 있다.

Next.js를 썼을 때는 fetch를 이용해야 캐싱 기능을 활용할 수 있어서 별 고민 없이 fetch를 이용했는데, 리액트에서는 axios를 사용하는 경우가 많은 것 같아서 왜 그런지? 알아보려고 한다.

 

https://axios-http.com/kr/docs/intro

 

시작하기 | Axios Docs

시작하기 브라우저와 node.js에서 사용할 수 있는 Promise 기반 HTTP 클라이언트 라이브러리 Axios란? Axios는 node.js와 브라우저를 위한 Promise 기반 HTTP 클라이언트 입니다. 그것은 동형 입니다(동일한 코

axios-http.com

 

 

fetch는 응답을 처리할 때 respose.json()으로 매번 JSON 형식으로 바꿔주는 작업이 필요한데, axios는 이런 처리를 자동으로 해준다. 또한 HTTP 에러 상태코드를 받았을 때, 조금 더 간단하게 에러를 처리할 수 있다는 장점이 있다고 한다.

사실 이런 부분은 말만 들어서는 잘 모르고 직접 써봐야 안다.

그래서 써봐야겠다.

 

사실 이전 프로젝트에서 fetch를 썼을 때도 모듈화 된 api를 이용했어서 에러처리가 그렇게 불편한지는...? 별로 못느낀 것 같다.

그래도 새로운 것을 써보고 싶기 때문에 axios를 사용해보도록 하자.

 

api 인스턴스 만들기

const api = axios.create({
  baseURL: "<http://localhost:3001/api>",
  timeout: 5000,
  headers: {
    "Content-Type": "application/json",
  },
});

axios를 사용하며 header에 토큰을 넣거나 param을 넣을 일이 많을 것 같은데, 재사용하기 좋아보여서 api instance를 만들어보았다.

const [chatRooms, setChatRooms] = useState<ChatRoom[]>([]);

  const fetchChatRooms = async () => {
    try {
      const response = await api.get<ChatRoom[]>("/rooms");
      setChatRooms(response.data);
    } catch (error) {
      if (axios.isAxiosError(error)) {
        const errorMessage =
          error.response?.data?.error ||
          "채팅방 목록을 불러오는데 실패했습니다.";
        console.error(errorMessage);
      } else {
        console.error("알 수 없는 에러가 발생했습니다.");
      }
    }
  };

  useEffect(() => {
    fetchChatRooms();
  }, []);

fetchChatRooms 내에서 axios를 이용해 get 호출을 하고 받은 데이터를 chatRooms state에 저장한다. 에러 핸들링도 해보았다.

  const fetchChatRooms = async () => {
    const result = await getChatRooms();
    setChatRooms(result);
  };
import axios from "axios";

const api = axios.create({
  baseURL: "<http://localhost:3001/api>",
  timeout: 5000,
  headers: {
    "Content-Type": "application/json",
  },
});

const handleError = (error: unknown) => {
  if (axios.isAxiosError(error)) {
    console.error(error.response?.data?.error);
  } else {
    console.error("알 수 없는 에러가 발생했습니다.");
  }
};

export const getChatRooms = async () => {
  try {
    const response = await api.get("/rooms");
    return response.data;
  } catch (error) {
    handleError(error);
  }
};

 

에러를 처리할 수 있는 부분이 반복될 것 같아 함수를 분리했다.

컴포넌트 내에서는 getChatRooms 한줄만 쓰면 데이터를 받아올 수 있다!