기존의 어플리케이션은 채팅방 데이터에서 읽어와서 채팅방이 없으면 그냥 에러를 띄워버렸다. 세상 무책임하네
친구 추가 기능도 업데이트 했겠다, 기존에 채팅방이 존재하지 않던 친구를 클릭하면 새로운 채팅방을 생성하는 기능을 만들어보기로 했다.
로직은 매우 간단하다. 기존에 채팅방에 입장하면 parameter로 받는 friendId
를 통해 채팅방 목록에서 해당 채팅방을 찾아 리턴한다. (getCurrentChatroom()
에서)
우선 chatroomContext
의 리듀서에 새로운 채팅방을 추가하는 코드를 추가하였다.
// chatroomContext.tsx
const chatroomListReducer = (
state: chatroomList,
action: chatroomListAction
): chatroomList => {
switch (action.type) {
case 'chatrooms/updateMessage':
const restChatrooms = state.filter(
(chatroom) => chatroom.friendId !== action.data.friendId
);
return [action.data, ...restChatrooms];
case 'chatrooms/createChatroom': // new chatroom
return [...state, { friendId: action.data, chats: [] }];
default:
return state;
}
};
그 다음 getCurrentChatroom()
에 다음과 같은 로직을 추가했다.
friendId
를 통해 채팅방을 찾는다chatroom
을 하나 생성한 다음에 그걸 리턴한다.// useChatroomContext.ts
const getCurrentChatroom = (friendId: number | string): chatroom => {
const chatroom = chatroomListContext.find(
(chatroom) => chatroom.friendId === parseInt(friendId as string)
);
if (!chatroom) {
chatroomListDispatch({
type: 'chatrooms/createChatroom',
data: friendId as number,
});
}
return chatroomListContext.find(
(chatroom) => chatroom.friendId === parseInt(friendId as string)
)!;
}
뭐 UI가 추가되는 것도 아니니 기능추가 그거 가볍네~ 라고 자신있게 말하며 테스트를 했으나,,,
왜그런것이지... 하고 계속 생각을 해봤는데,,
항상 까먹는 문제가 있다, 리액트에서 상태변경 (setState
, useState
, useReducer
의 dispatch
등...)은 항상 비동기로 동작 한다는 사실을...
상태를 변경하고 그 함수가 끝나기도 전에 바로 상태를 사용하려고 하니 당연히 못찾지... 그래서 방법을 고민하다가 결국 chatroomContext
에서 필터링을 해서 리턴하지 말고 첫번째에는 그냥 chatroom
이랑 똑같이 생긴 객체만 리턴해도 괜찮지 않나? 하는 생각이 들었다.
// useChatroomContext.ts
const getCurrentChatroom = (friendId: number): chatroom => {
const chatroom = chatroomListContext.find(
(chatroom) => chatroom.friendId === friendId
)!;
if (!chatroom) {
chatroomListDispatch({
type: 'chatrooms/createChatroom',
data: friendId as number,
});
return {
friendId: friendId,
chats: [],
} as chatroom;
}
return chatroomListContext.find(
(chatroom) => chatroom.friendId === friendId
)!;
};
그러고 나니 아주 깔끔하게 채팅방이 들어가진다.
머스크형이랑 짧은 대화도 나눴다
나 자신도 결국 유저 목록에 있기 때문에 나와의 채팅방 또한 생성된다. 원래 헤더 프로필을 누르면 채팅 보내는 유저가 토글되는데, 이건 나와 나의 채팅이니까 눌러도 바뀌진 않는다. 얼떨결에 나와의 채팅까지 구현....^^
두가지의 문제가 있는데, 원인은 같은 것으로 예상이 된다만...
채팅방을 새롭게 생성하고 아무런 채팅도 하지 않고 나가게 되면, 채팅방이 두개가 생긴다...
머스크형이 두명,,, 트위터 헛소리도 두배...
물론 채팅방에 들어가서 채팅을 치면 다시 한개로 정상작동한다.
디버거를 통해 디버깅을 해보았더니,,, 컴포넌트 함수가 두번씩 실행되는 것이지 않는가... 홍진호도 아니고 왜 두번씩 실행되는거지 싶어 원인을 찾아보았다.
CRA로 프로젝트를 생성하고 index.tsx
를 보면 <App>
이 <React.StrictMode>
로 감싸져있는것을 볼 수있다. strict mode
로 동작을 한다는 의미인데, 리액트는 development 환경 에서 strict mode
로 동작시 예상못한 부작용을 찾는데 도움을 주기 위해서 아래의 것들을 두번씩 실행한다고 한다.
constructor
, render
그리고 shouldComponentUpdate
메서드getDerivedStateFromProps
static 메서드setState
의 첫 번째 인자)useState
, useMemo
그리고 useReducer
에 전달되는 함수물론 production 환경에서는 한번씩만 실행한다.
자세한 내용은 공식문서 참고
나의 문제가 발생하는 이유는 getCurrentChatroom()
이 순수함수가 아니기 때문이었다. friendId
가 같더라도, 최초에 만들어지는지, 아니면 이미 존재하는지에 따라 chatroomContext
를 선택적으로 업데이트한다. 따라서 이런 의도치 못한 동작이 발생하는 것 같다. 이는 더 좋은 방법을 고민해봐야 할 것 같다.
존재하지 않던 채팅방을 처음에 입장하게 되면 아래와 같은 경고가 발생한다.
Chatroom
을 렌더하면서 ChatroomContextProvider
컴포넌트를 업데이트 하지 말라는 의미인데, 리액트 v16.13.0 부터 (참고) 이런 식으로 렌더중인 컴포넌트 외의 컴포넌트의 업데이트는 사이드 이펙트의 위험이 있기 때문에 경고를 띄우게 된다. 리액트 공식 깃허브에 올라온 이슈에 따르면 useEffect
를 통해 업데이트 하라고 하는데, 아직은 어떻게 구조를 짜야할지 감이 잘 안온다. 이 부분도 공부해봐야 할 것 같다.