Randomly 프로젝트 - 팀원 피드백 기능, React로 모달창 구현
🏡 GitHub : https://github.com/soohyun-dev/Randomly
오늘 정리할 것은 그동안 구현한 팀원 피드백 기능이랑 그 과정에서 모달창을 구현한 방법 및 트러블 슈팅에 대한 것입니다. 😊
❓ 무엇을 구현했나요
팀원 피드백 기능을 구현했습니다.
이 기능은 실제 면접 스터디를 하면서 각 팀원끼리 실시한 모의 면접에 대한 피드백을 주기 위한 것입니다.
위의 사진에서 확인할 수 있듯이 피드백에 대한 대상이 있고 피드백을 주는 대상이 있습니다.
이 기능은 현재 로그인을 하지 않아도 사용할 수 있게 하였습니다.
이유는 좀 더 많은 유저들이 사용할 수 있도록 편의를 제공하기 위함입니다.
아무래도 로그인을 해서 사용해야하는 서비스는 막 필요한 경우가 아니면 안쓰게 되더라고요 😅
그래서 로그인 없이도 사용할 수 있게 한다면, 한 명이라도 더 사용해보지 않을까? 싶은 생각이 들었습니다.
또한, 이 기능에선 평가자가 기본으로 익명으로 설정됩니다.
익명으로 설정한 이유도 있는데요.
우리가 누군가를 평가할 때 실명을 걸고 정말 날카로운 평가를 하기가 어려운 순간들이 있습니다.
물론 가능한 경우가 있지만, 친하지 않다던가 상대와의 관계를 고려할 때 표현이 다소 조심스러워지는 부분이 생기면서 이는 정확한 피드백을 주는 것을 어렵게 만드는 상황들로 이어집니다.
따라서, 다수의 집단에서 익명의 메세지를 통해 피드백을 전달한다면 보다 더 날카로운 피드백을 유도할 수 있다고 생각합니다.
물론, 이는 욕설 및 지나친 표현들에 대한 부분들까지 나타날 수 있게되는 문제점들이 있어 양날의 검과 같은 기능이라고 생각합니다.
이를 방지하기 위해서는 신고방이나 필터링 같은 기능이 또 추가되어야겠지요?
어떠한 방법이 가장 최선의 방법일까 고민한 끝에 익명 제도를 실시하기로 하였습니다.
익명을 우선으로 부여하지만, 실명이나 별명을 통해 기록을 하시고 싶은 경우들도 고려하여 input에 사용자가 정의한 이름대로 데이터가 저장될 수 있도록 하였습니다.
이 부분에 대해서는 이 기능을 사용하는 집단 내에서 따로 규칙을 정하여 진행하는 방법이 좋을 듯합니다.
👨💻 사용 방법
기본적으로 이 기능은 면접 스터디에서 사용하기 적합하게 만들었습니다.
면접관 역할들을 하는 팀원들은 면접자 역할을 하는 팀원의 대답을 청취하고 팀원의 대답에 대한 느낀점들을 기록합니다.
스터디를 진행한 이후, 면접자 역할을 한 팀원은 Review 페이지에서 자신의 대답에 대한 각 팀원들의 피드백들을 확인할 수 있습니다.
이는 자신의 부족한 부분들을 체크하고 보완하면서 더 성장해 나아갈 수 있게하기 위함입니다.
위와 같이 자기소개에 대한 피드백, 질문에 대한 피드백으로 나누어져 있고 상대방에 대한 기록을 이곳에 남기게 되고 확인할 수 있게 됩니다.
❓ 페이지 OR 모달창
피드백을 보여주는 페이지를 하나의 페이지로 가져갈 것인지 모달창으로 보여줄 것인지에 대해 고민이 되었습니다.
몇가지 경우들을 생각한 끝에 모달창으로 구현하기로 결정하였는데요.
이유는 다음과 같습니다.
1. 한번에 여러명이 평가하는 부분이다 보니 그에 대한 피드백을 여러번 확인해야한다.
예를 들어 N명의 평가자들이 평가를 한다하면 결과물이 N개 나오게 됩니다.
물론 하나로 통일할 수도 있지만, 개인 각자의 피드백을 담기에는 각자의 피드백을 기록하는 것이 더 좋다고 느꼈습니다.
그렇다면 N개의 피드백을 N개의 페이지로 확인하는 것보다 하나하나 모달창으로 열람하는 것이 낫겠다라고 생각했습니다.
이 부분은 여러 방향이 있겠다라고 생각합니다. 물론 여러 피드백을 한 페이지에 정리할 수도 있습니다.
그 부분에 대해서도 장점이 있겠지만, 모달창에 대한 장점이 저는 더 크게 느껴졌습니다.
2. 모달창을 구현해보고 싶다.
사실 프로젝트를 여태하면서 React로 모달창을 제대로 구현해본적이 없었습니다.
바닐라 자바스크립트로만 구현을 해봐서 이 기회에 React로 모달창을 직접 구현해보고 싶었습니다.
또한, 과제를 구현하는 스터디에서 이번주에 바닐라 자바스크립트로 모달창을 열고닫는 구현을 하게 되었는데, 이때 구현했던 방법으로 이 프로젝트에 그대로 녹여내고 싶었습니다.
3. 사용자 경험에 좋다.
이 부분은 개인적인 부분입니다.
모달창은 페이지보다 좀 더 가벼운 느낌입니다.
이 가벼움이 줄 수 있는 사용자 경험의 장점에 대해 생각해봤습니다.
사실 피드백에 대한 부분은 거부감이 들 수도 있다고 생각합니다.
어찌보면 누군가가 나에 대해 지적한 글을 담은 것이잖아요?
그래서 모든 글이 사용자에 대한 기분을 좋게 만들 수는 없다고 생각합니다.
따라서, 사용자가 원하는 시점에 빨리 내용을 보여주고 원하는 시점에 빨리 내용을 닫아버릴 수 있다는 부분에서 좀 더 모달창이 이 기능을 구현하는데 있어 적합하지 않나 싶습니다.
👨💻 구현
❓ 어디서 입력 받을래
우선 팀원에 대한 피드백을 어느 페이지에서 입력을 받을 것인지에 대해 고민을 하였습니다.
질문을 하는 페이지에서? 아니면 피드백을 모아놓은 페이지에서?
한 번 스터디에서 진행을 해보고 느끼는 것을 토대로 하는 것이 낫겠다라고 생각하고 경험해봤습니다.
제가 느낀점은 질문을 하는 페이지에서 적고 제출할 수 있게 하는 것이 좋겠다라고 생각합니다.
아무래도 질문을 하는 와중에 피드백을 계속 적을 것이잖아요? 한 번에 몰아 적기 보단.
따라서, 그 페이지에서 바로바로 적을 수 있는 것이 좋겠다라고 느꼈습니다.
따라서 작성페이지는 질문을 보여주는 PlayInterview 페이지에서 작성하기로 결정!!
❓ 어떻게 보여줄래
입력된 피드백 데이터들은 firebase에 저장됩니다.
이후 Review 페이지에 접속하면 위와 같이 미리보기로 각 피드백들이 나열됩니다.
각 미리보기 글은 50자까지만 미리볼 수 있게 설정하였습니다.
또한, 정렬은 등록된 시간 순으로 내림차순으로 보여집니다.
useReview.tsx
저장된 데이터를 Review 페이지에서 불러올 때는 React-Query를 사용하였습니다.
review라는 키를 가지고 해당 데이터를 불러와 캐싱하는 역할을 합니다.
저번 포스팅에서 작성하였듯이 비동기 로직 처리도 동일하게 진행하여 초기 undefined가 할당되는 값을 처리해주었습니다.
지금은 저장된 데이터들이 별로 없어서 그대로 보여지지만, 추후 페이지네이션도 구현해야겠습니다.
우선은 사용자가 자신에 대한 정보를 쉽게 찾을 수 있게하기 위하여 검색 기능을 구현하였습니다.
위처럼 검색 결과에 따라 잘 나오는 것을 확인할 수 있습니다.
검색 대상은 평가자가 아닌 평가 대상자입니다.
아무래도 평가자는 익명인 경우들이 있으니 검색 대상으로는 부적절합니다.
검색 기능은 완전탐색 알고리즘을 사용하였습니다.
O(N) 시간복잡도 내에서 처리할 수 있고, 아직은 작은 프로젝트이다보니 기능 구현에 전혀 문제가 없는 방법입니다.
다만, 상단 창에 대한 디자인인 마음에 안듭니다 ...ㅡ.ㅡ
아무래도 혼자 다하다보니 디자인도 제가 하게 되는데 영 별로네요~ 배경이라도 더 깔아봐야겠습니다.
디자인부분은 계속적으로 유지보수할 예정!
🤔 모달창 구현
사용자에 대한 정보를 보여주기 위해 모달창 구현을 진행하였는데, 쉽지만은 않았습니다.
몇몇 문제들이 있었는데요.
우선, 모달창을 구현하기 위해 고려해야할 사항들은 다음과 같습니다.
- 포스팅을 클릭시 해당 포스팅에 대한 데이터만 모달창으로 보여준다.
- 모달창 영역 외부를 클릭시 창이 닫히게
- 모달창이 열릴시 외부 스크롤 차단
- 모달창이 열릴 시 외부 영역들은 opacity 처리
차례대로 과정을 설명해보겠습니다.
1️⃣ 포스팅을 클릭시 해당 포스팅에 대한 데이터만 모달창으로 보여준다.
우선, 기본적으로 Review 페이지에서 각 피드백에 대한 데이터들을 로드하고 보여줍니다.
React-Query로 불러온 데이터들을 map을 통해 순회하면서 ReviewPosting에 props로 내려주면서 리스트를 만듭니다.
포스팅을 클릭하면 해당 포스팅을 모달창으로 띄우기 위해
onClick 이벤트를 이용하여 showModal함수를 호출합니다.
❗ 첫번째 문제
여기서 첫번째 문제가 발생합니다.
그동안 Div 태그에 onClick 이벤트를 바인딩할 수 있다고 알고 있는데, 이상하게 ReviewPosting에 건 onClick 이벤트가 호출이 안되는 상황
🚩 첫번째 문제 해결
알고보니 Review 페이지에서 onClick 이벤트만 걸면 안되고, ReviewPosting 내에서도 이 onClick에 대한 이벤트를 처리해줘야했습니다
처음에는 ReviewPosting내에 button 태그가 없어서 그런가하고 button 태그를 만들어서 구현했지만, 추후 굳이 버튼 태그가 없어도 된다는 것을 느껴 삭제했습니다.
ReviewPosting 내에서 clickHandler를 사용해서 props로 내려준 showModal을 실행시킵니다.
Review.tsx
Review.tsx 내의 showModal에서 포스팅이 클릭되면 모달창을 여는 작업들을 실시합니다.
setShowData
- 열 포스팅에 대한 데이터를 저장
setModalOpen
- Review 페이지 내에서 모달이 열렸는지에 대한 정보 저장
setIsFirstRender
- 모달창이 열린 상태로 다른 페이지로 가면 다시돌아왔을때 모달창이 열린 상태로 남아있게되는 것을 방지하기 위해 Review 페이지 접속시 처음렌더링 되는 상황을 구분하기 위해 필요한 값이다.
- 나는 RTK를 사용해서 모달이 열렸는지를 또한번 관리하고 있어서 필요한 작업이다.
- 만약, 전역 상태관리를 안하고 있다면, 필요없을 것이다.
dispatch
- 모달이 열렸는지에 대한 여부를 RTK 전역 상태관리
- 해당 값을 통해 GlobalStyle이나 ReviewDetail 컴포넌트에서 처리를 하게된다.
해당값은 다크모드를 관리하고 있는 themeSlice에서 같이 관리됩니다.
초기값은 isModalOpen: false 입니다.
이제 모달창이 열리면 ReviewDetail 컴포넌트를 렌더링하여 화면에 보여지게 합니다.
모달창 CSS는 위와 같습니다.
추가적인 CSS는 디자인에 따라 다 다를것이므로 따로 첨부하지는 않겠습니다.
2️⃣ 모달창 영역 외부를 클릭시 창이 닫히게
모달창 외부를 클릭하면 닫히게 하는 기능을 구현하고 싶었습니다.
이번주에 스터디에서 타입스크립트 구현 과제로 진행했던 부분이라 바로 적용해보기로 결정!
❗ 두번째 문제
어디서 처리를 해야할까??
Review 페이지에서 처리를 해야하는지 모달창 컴포넌트인 ReviewDetail 에서 처리해야하는지 헷갈렸습니다.
둘다 해봤는데, DOM을 조작해야하다 보니 Review 페이지에서 처리를 하게되면 ref를 ReviewDetail 컴포넌트에 내려주고 처리하고 이런 작업들을 해야해서 불편하더라고요.
ReviewDetail 컴포넌트 내에서 처리하면 해당 컴포넌트 내에서 처리를 끝낼 수 있어서 훨씬 간편하다고 느꼈습니다.
🚩 두번째 문제 해결
우선 reviewContainer에 body 를 담아줍니다.
이후 모달창 영역을 isDetailBox에 담아줘야합니다. 초기값은 null로 설정합니다. (빈값을 주면 에러가 납니다.)
위와 같이 모달창 최상위에 ref를 걸어줘서 DOM 구조를 저장합니다.
모달창 컴포넌트 내에서 useEffect로 처리를합니다.
로직을 간단히 설명해 보겠습니다.
처음 모달창에 렌더링되면, window 객체에 click이벤트로 modalCloseHandler를 바인딩합니다.
이렇게되면 모달창이 뜬 상태에선 window객체 내의 어떠한 영역을 선택하든 modalCloseHandler가 실행됩니다.
modalCloseHandler가 실행되면 다음과 경우를 검사합니다.
- 모달창이 열렸니? => isModalOpen
- 선택된 영역이 window 객체 내에 속하니? => reviewContainer.current.contains(target)
- 선택한 영역이 모달창 영역 내에 속하니? => isDetailBox.current.contains(target)
- 마운트된 상황 이후이니? => isFirstRender
우리는 모달창 영역 외부를 클릭하면 닫히게 해야하는 경우이므로 다음의 값들이 나와야합니다.
1번 => true
2번 => true
3번 => false
4번 => false
참고로 4번은 초기에 모달창이 마운트 된 이후에 modalCloseHandler가 실행되게 하기 위함입니다.
이 값을 설정해주지 않으면 모달창이 켜짐과 동시에 꺼지게 되는 오류가 발생합니다.
이 부분은 어떻게 구현했는지에 따라 달라질 것 같아서 4번은 옵션으로 가져가면 됩니다.
위와 같이 구현을 하니 모달창 이외의 영역(navbar 포함)을 선택하면 모달창이 잘 닫힙니다. 😚
3️⃣ 모달창이 열릴시 외부 스크롤 차단
이건 금방 구현할 수 있습니다.
ReviewDetail.tsx
모달창 컴포넌트 내에 useEffect로 위와 같이 설정을 걸어주면 됩니다.
4️⃣ 모달창이 열릴 시 외부 영역들은 opacity 처리
가장 고민이 많이 되고 구현에 신경쓰인 부분입니다. 🤔
모달창을 구현하면서 가장 핵심인 부분이 아닐까 싶네요.
❗ 세번째 문제
처음에 최상단 부모에 opacity를 주고 자식에서 opacity를 무시하면 되겠지 하고 실행했는데 위와 같이 자식 컴포넌트에서 opacity를 무시 못하는 것입니다.
!important를 사용해도 안되고 opacity대신 background 영역을 건드려봐도 결과는 똑같았습니다.
대체 왜 안되는거지?
상식적으로 무시가 되어야하는데 안되는 것입니다.
🚩 세번째 문제 해결
이유는 생각보다 어이없었습니다.
바로 그냥 부모가 아닌 부모의 부모에 opacity를 건 탓....
초기 ReviewDetail은 ReviewPostingListSection이라는 부모 밑에 있었습니다.
내가 opacity를 걸어준 부분은 ReviewPostingListSection의 부모인 ReviewAllSection이라는 부분입니다.
결과적으로 ReviewDetail의 부모의 부모에 opacity가 걸려있는 상황.
따라서 그 자식의 자식에서 opacity를 무시하려고 별짓을 해도 안되는 것이였습니다.
background를 설정해주면 어찌저찌 먹히긴하는데 이렇게 하면 다른 컴포넌트들도 모두 설정해줘야해서 너무 복잡했습니다.
ReviewDetail은 모달창이라 사실 ReviewPostingListSection 부모 밑에 있을 필요가 없었습니다.
따라서, 다음과 같이 구조를 변경하였습니다.
ReviewDetail을 ReviewSection, ReviewPostingListSection 이랑 같은 형제로 변경해주었고 부모를 ReviewAllSection로 만들어주었습니다.
이후 모달창이 열리면, ReviewPostingListSection 에 opacity를 주는 방법으로 변경하였습니다.
위와 같이 작성하니
제가 원하는대로 잘 동작하였습니다.
추가적으로 NavBar에도 opacity를 따로 주었습니다.
❗ 네번째 문제
동작은 잘되었는데, 모달창이 뜰때 특정 스크롤 위치에서는 저렇게 다른 영역이 표시되는 오류가 발생했습니다.
저 부분을 모달창이 열릴때 안보이게 했으면 좋겠는데....
🚩 네번째 문제 해결
생각보다 간단하게 해결 !
그냥 모달창이 열릴때 opacity를 0 줘버리면 되었습니다.
👨💻 후기
결과적으로 내가 원하는 팀원 리뷰 페이지 및 모달창을 잘 구현할 수 있게 되었습니다.
모달창을 React로 처음 구현해보면서 모달창을 동작할 때 생각해봐야하는 부분들에 대해서 경험을 쌓을 수 있어서 좋았네요.
사용자의 편의측에서 좀 더 많은 생각을 하면서 구현에 신경 쓰게 되었습니다.
안에 들어갈 내용들에 대해서는 좀 더 생각이 필요할 것 같지만, 일단 초기 구현 상태는 성공적으로 완료했네요!! 😊
이제 추가로 리스트 페이지네이션이랑 입력을 받는 input 창 스타일링을 해야겠습니다.
'성장기록 > 프로젝트' 카테고리의 다른 글
Randomly 프로젝트 - React Query 데이터 캐싱 올바르게 사용하기 (0) | 2023.05.05 |
---|---|
Remind - 인플루언서 체험단/협찬 관리 서비스 프로젝트 (0) | 2023.04.11 |
Randomly 프로젝트 - React Query로 비동기 처리하기, 상태(state) 다루기 (1) | 2023.03.22 |
Randomly 프로젝트 - Storybook 적용기 (0) | 2023.03.19 |
Randomly 프로젝트 - React-Query 적용기 (0) | 2023.03.19 |