개발 공부 기록

나는 무엇을 하는가?

프로젝트/chat-app

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

진!!!!! 2024. 12. 8. 20:07

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

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

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

 

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

 

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 한줄만 쓰면 데이터를 받아올 수 있다!