추후에 두가지로 상태가 변하는 Nav 에 대해서 다루겠지만, 그 전에 어떠한 상태에서든지 우측 상단에는 프로필 버튼이 있다.
추후 로그인을 할때나 로그아웃, 마이페이지, 혹은 예약상태 등등 추가기능이 구현될 경우 navigate 를 걸어줘야 하기 때문에 당연히 만들었어야 했고, 로그인 상태와 비 로그인 상태에 따라 생성되는 모달창 역시 컴포넌트로 나누어서 구성하였다.
고려해야 할 점은 모달창이 열리고 닫히는 것과, 2가지 다른 모달창이 구별되어 나와야 하는 점이다.
더 좋은 방법이 있을 것 같지만, 당시에 생각했을 때는 2가지의 nav 형태에서 모두 버튼에 모달창이 연결되어있어야 하고, 같은 state 변화로 관리를 하는게 효율적이라 판단했다. 그래서 크게 나눠보면
- profileModal : 네이밍을 좀 잘못했지만, 이 상태가 위 사진에 보이는 모달창의 열고 닫고를 boolean 으로 결정하는 state
- modalIsOpen : 위 모달창에서 로그인 버튼을 클릭시 나오는 모달창(로그인창)을 열고 닫고 결정한다.
이렇게 2개의 state 로 각각 따로 관리하려고 하였고(첫 변수이름 지을때 너무 대충짓긴 했다.. ) 시나리오를 짜보면
- Nav 에서 버튼을 클릭하면, 옵션 선택 모달창이 나오게 된다
- 옵션 선택 모달창(profileModal) 에서 어떠한 옵션을 클릭 or 주변 overlay 클릭 or 버튼 클릭 시
profileModal = false 로 하여 닫히게 한다. - 옵션 중 '로그인' 클릭 시 위 사진처럼 로그인 창이 나오게 되고 이때는 이미 profileModal 은 false 상태
(modalIsOpen = true 상태) - 이제 나머지는 위 로그인 창에서 닫기를 누르면 modalIsOpen = false 로 변화
내 생각에는 이게 최선이라고 생각하였다. 그리고 2가지 state는 전역적으로 관리가 되어야 하기 때문에, 가장 최상 부모인 Nav 에서 props 로 뿌려주는 설계를 하였다.
const [profileModal, setProfileModal] = useState(false);
const [modalIsOpen, setModalIsOpen] = useState(false);
const switchModal = () => {
setProfileModal(prev => !prev);
setModalIsOpen(prev => !prev);
};
그러면 여기서 switchModal 함수는 어디에 들어가는가?
const ProfileContainer = ({setProfileModal, switchModal}) = {
return (
<>
<ModalOverlayInUserInfo onClick={() => setProfileModal(false)} />
<ModalProfile onClick={e => e.stopPropagation()}>
<TopContainer>
<UserInfoMenu onClick={switchModal}>
<InfoMenuLogin>로그인</InfoMenuLogin>
</UserInfoMenu>
<UserInfoMenu>
<InfoMenuText>회원가입</InfoMenuText>
</UserInfoMenu>
</TopContainer>
<DivLine />
<BottomContainer>
<UserInfoMenu>
<InfoMenuText>숙소 호스트되기</InfoMenuText>
</UserInfoMenu>
<UserInfoMenu>
<InfoMenuText>체험 호스팅하기</InfoMenuText>
</UserInfoMenu>
<UserInfoMenu>
<InfoMenuText>도움말</InfoMenuText>
</UserInfoMenu>
</BottomContainer>
</ModalProfile>
</>
);
}
위처럼 로그인 버튼에 onClick 함수로 들어가게 된다. 앞서 설명한대로 로그인 버튼을 클릭시 기존 모달창은 꺼져야 하고, 새로운 로그인 모달창이 나타나야 하기에, 동시에 state 를 변경해줘야 한다. 따라서 위처럼 switchModal 함수를 생성하여 넘겨준 것이다.
그리고 이렇게 작업을 하다가 문제가 생긴 부분중 하나가,
만일 profileModal 창을 띄우고 그 다음 로그인 버튼을 누르려고 할 때, 이미 overlay 클릭으로 간주되어서 로그인버튼이 클릭이 되지 않은 경우가 발생한다. 이러한 현상은 overlay의 클릭 이벤트가 버블링 되어 나타나는 현상이다.
쉽게 말해 클릭한것은 로그인 버튼인데 그 클릭이 타고 올라가 overlay 까지 도달하여 이때의 onClick 이벤트가 발생해버린샘.
..사실 완벽히는 이해가 되지 않았던 것은, 보통 이벤트 버블링은 부모 자식간의 관계속에서 이뤄지는걸로 알고 있어서, 모달창과 overlay는 부모 자식관계가 아니어서 괜찮지 않을까 싶었다. 다만 retrun 안에 넣기 위해 <> 를 추가하였는데 이를 부모라고 여기면 또 가능하니 그렇게 이해했다..
즉 로그인을 클릭했을 때의 클릭 이벤트가 부모까지 타고 올라가는걸 막아줘야 한다.
이를 위해 필요한 것이 event.stopPropagation() 이다. 자식부분에 onClick 으로 걸어주면 버블링을 막아줄 수 있다. 실제로 위 코드에 이렇게 작업을 해주니, 더이상 로그인버튼을 클릭해도 바로 모달창이 꺼지는 현상은 사라졌다.
지금까지의 과정을 통해 모달창 열고 닫기 부분은 해결이 되었다. 이제 또 하나 중요한 것을 고려해야한다.
로그인 이전과 이후를 구별해야한다.
로그인을 하기 전에 메인창에서 우측 프로필 버튼을 클릭할 시 나오는 모달창은 위에서 모두 다루었다. 즉, 로그인 모달창까지의 연결과정을 위에서 다루었다면, 이제 로그인 후 프로필 버튼을 누를 시 나오는 모달창들에 대해서 다루어야 하고, 이를 조건식으로 구별하여 상황에 맞는 모달창이 나타나도록 설정해야 한다.
로그인이 되어있다면, 그에 따라 버튼을 누를 시 나타나는 옵션 선택 모달창은 아래와 같다.
당연한 기능이지만, 내가 당연하다고 해서 컴퓨터가 그것을 그대로 알아들을리는 없다. 설정을 해주어야 한다.
즉, 내가 로그인 되어있다는 것을 웹 브라우저에게 알려 주면 될거 같았다. 어떻게 알려줄 지 고민해보다가, 가장 친숙한 방법을 사용하기로 했다.
페이지에서는 인증과 그에따른 허용되는 페이지가 있고, 그렇지 않은 경우가 있다. 예를 들자면 내가 만약 로그인을 하지 않았다면, 상세페이지에서 예약버튼을 누르는 순간(사이트마다 차이가 있겠지만) 보통은 '로그인이 필요' 하니 결제 페이지 대신 로그인 페이지로 이동 될 것이다.
아무런 불편함 없이 사용하던 웹 사이트들도 사실 이렇게 다 하나하나 변수를 고려하여 그에 맞게 설정을 해준것이고, 이러한 인증인가를 다루는 방법도 여러가지가 있다고 들은 적은 있다. 아직 배우지 않아서 다음에 더 자세히 알아보고 실제로 적용을 해봐야 겠지만, 우선 지금은 그러한 인증인가 과정에서 잘 이용되는 유저 token 을 모달창에도 이용을 해보려 한다.
로그인에 성공하면 서버로부터 token 을 가져올 수 있다. 유저에게 할당된 유일한 값이므로, 보통은 이를 localStorage 나 sessionStorage 에 저장을 해놓는다. (쿠키에다가도 할 수 있다) 그 다음 필요한상황마다 저장된 토큰값을 조건으로 이용할 수가 있다.
이번 모달창의 전환 역시 이 token 을 이용하고자 한다.
const Nav = () => {
const [startDate, setStartDate] = useState(null);
const [endDate, setEndDate] = useState(null);
const [location, setLocation] = useState('지도표시지역');
const [guest, setGuest] = useState(0);
const [toggleNavbar, setToggleNavbar] = useState(true);
const [profileModal, setProfileModal] = useState(false);
const [modalIsOpen, setModalIsOpen] = useState(false);
const [signupIsOpen, setSignupIsOpen] = useState(true);
const [isToken, setIsToken] = useState(false);
const modalRef = useRef();
useEffect(() => {
if (localStorage.getItem('Token')) {
setIsToken(true);
} else {
setIsToken(false);
}
}, [isToken]);
토큰값이 존재한다면 isToken state 가 true 가 되고 이런식으로 boolean 으로 설정이 가능하다.
여기서 의존성 배열에 isToken 을 넣어줬는데, esplint 를 해결하기 위함도 있고, 원래 의도는 상태값이 변화할 때마다 useEffect 를 돌리기 위함이었는데, 지금 생각해보니 굳이 그러지 말고 로그인 됬을 화면에서와, 로그아웃 하는 화면에서 각각 setIsToken 을 걸어주면 되는거 아닌가 생각이 든다... 나중에 다시 확인해봐야 할듯하다. 예를 들자면 아래 코드처럼 로그인 화면에서 원래는 setIsToken 을 설정해주지 않았는데, 아래처럼 설정을 해준다면 되지 않을까 싶다.
const KakaoLogin = ({setIsToken}) => {
const location = useLocation();
const navigate = useNavigate();
const KAKAO_CODE = location.search.split('=')[1];
const goSign = () => {
fetch(`${BASE_URL}/users/kakao/oauth`, {
method: 'GET',
headers: { Authorization: localStorage.getItem('key') },
})
.then(res => res.json())
.then(
data => (
localStorage.setItem('Token', data.token),
alert('환영합니다'),
window.location.replace('/')
setIsTeken(true)
)
);
};
여튼 다시 돌아와서 로그아웃 시에는 확실히 isToken 값을 false 로 바꿔줘야 한다. 따라서 아래처럼
const ProfileLoginContainer = ({
profileModal,
setProfileModal,
isToken,
setIsToken,
switchModal,
}) => {
const navigate = useNavigate();
const deleteToken = () => {
localStorage.removeItem('Token');
localStorage.removeItem('key');
alert('로그아웃 되었습니다.');
setIsToken(false);
setProfileModal(false);
};
토큰을 지워주고, 모달창을 닫으며, isToken 을 false 로 바꾸어준다. (사실 로그아웃에 저 setIsToken 을 상태변화 시켜줄 때 이미 useEffect 에 의존성 배열을 채워주는게 의미가 있을지는 모르겠다...)
이렇게 상황에 따라 isToken 의 값이 변하게 되고, 이 값에 따라서 화면에 보여질 모달창을 골라주면 된다.
const swtichProfileModal = {
1: (
<ProfileContainer
profileModal={profileModal}
setProfileModal={setProfileModal}
modalIsOpen={modalIsOpen}
setModalIsOpen={setModalIsOpen}
switchModal={switchModal}
isToken={isToken}
setIsToken={setIsToken}
/>
),
2: (
<ProfileLoginContainer
profileModal={profileModal}
setProfileModal={setProfileModal}
isToken={isToken}
setIsToken={setIsToken}
switchModal={switchModal}
/>
),
return (
// 생략
</UserInfoContainer>
{profileModal &&
(isToken ? swtichProfileModal['2'] : swtichProfileModal['1'])}
</InfoPositionSet>
// 생략
)
위 코드는 nav 중 하나의 변수와 return 값 중 일부를 가져온 것이다. (저번에 얘기했듯 에어비엔비 nav 는 2가지 형태로 나뉜다. 여긴 그 중 하나의 형태이며 나머지도 위 코드는 동일하다)
하나의 변수를 지정해 객체로 저장하고, 객체 키값 1,2 에 대한 value 값으로 각각의 프로필 모달컴포넌트를 지정해준다. 즉 로그인 되기 전 보여줄 모달창과, 로그인 되고 나서의 모달창을 나눠서 가져오고, 조건에 따라 다른 컴포넌트를 불러주는 것이다.
profileModal 이 true 라는 것은 위에서 다루었듯이 버튼을 눌렀을 때 모달창이 나오게 하는 것이고, 이후 isToken 이라는 조건을 걸어서 true 라면 로그인이 된 후 프로필 모달창을 가져오고, false 라면 로그인 되기 전 프로필 모달창을 가져오도록 조건부 랜더링을 돌린다.
뭔가 계속 구조가 많이 나오고 있어 햇갈릴 수 있다. 전체 구조가 다시 기억이 나지 않는다면 첫번째 글을 살펴보면 이해가 갈 것이다.
더 남아있지만, 쓰면서도 좀 복잡해서...
조금 아쉬움이 드는 것은, 당시에 코드를 작성하면서 블로그에 리뷰를 남기는 식이었다면, 좀 더 정확학 설명이 가능했을 텐데 지금은 솔직히 내가 짠 코드도 잘 해석이 안되서 쓰는데 애를 먹고 있다.
솔직히 코드를 보다 깜짝 놀랄때가 있는데, 내가 이걸 왜이렇게 했었지? 하면서 어쩔땐 스스로에게 대단함을 느끼고, 어쩔때는 이땐 왜 이정도밖에 생각하지 못했지 라고 탄식할 때도 있다.
뭐 이렇게 생각해보면, 조금 시간이 지나 다시 코드를 리뷰해보는 지금 이 시간이 더 도움이 되는 거 같기도...?
아무튼 아마 한 2~3개의 리뷰글을 적으면 Nav 파트는 끝이 날 것 같다. 그 다음 Filter 로 넘어가게 될텐데, 이 부분은 리뷰하기 전에 리펙토링이 먼저 이루어져야 할 것 같기도 하고, 그래도 프로젝트 때 했던 코드를 그대로 가져와야 하는 거겠지 하며 고민이 된다. 뭐 이건 그때 가서 천천히 생각해보고, 다음에는 로그인 전 후 모달창의 코드에 대해서 좀 더 살펴보겠다.
'Programing > React' 카테고리의 다른 글
2차 프로젝트 - 에어비엔비 2개의 Nav로 구성하기(6) (0) | 2022.09.14 |
---|---|
2차 프로젝트 - 에어비엔비 ProfileContainer(5) (0) | 2022.09.04 |
2차 프로젝트 - 에어비엔비 상단 검색창 - Location, Calender, User(3) (2) | 2022.08.31 |
2차 프로젝트 - 에어비엔비 상단 검색창 - Search(2) (0) | 2022.08.30 |
2차 프로젝트 - 에어비엔비 상단 검색창(1) (0) | 2022.08.24 |