Modal을 처음 구현하는 나에게 airbnb의 내비게이션 바의 첫 인상은 별 거 아니네...였다. 이게 나를 새벽코딩 시킬 줄이야....
처음에는 react-modal 라이브러리로 모달을 구현했다. 너무 간편하고 쉬웠다. 하지만 하나의 모달이 아닌 6개의 모달을 구현해야하는 상황에서 라이브러리를 통한 modal의 state관리가 너무 복잡했다. 그렇게 고민하다가 "와...그냥 내가 만들고 말지!!"
내가 스스로 모달창을 만들면 내 입맛대로 state, style 등을 통제할 수 있다.
지금 생각하니 모달 라이브러리를 삭제한 것은 신의 한 수!!
라이브러리가 능사는 아니다!! 가능하다면 내가 직접 구현하는 것도 아주 좋은 경험!! 코딩도 늘고 내 맘대로 통제 가능!!
로그인 버튼을 클릭하면 카카오 로그인 모달창이 그려지지만 독립적인 하나의 모달이기 때문에 별 다른 에러나 수고사항 없이 구현할 수 있었다.
하지만 Search Bar에는 모달 안에 모달이 있기 때문에 state를 props로 넘겨주는 과정과 여기를 클릭하면 모든 모달창이 꺼지고 저기를 클릭하면 하나의 모달창만 꺼지고 등의 함수를 작성하는 과정을 남겨보려 한다.
구조 파악
1. Nav.js (최상위)
2. SearchClickedModal.js (Nav 클릭 시 나타나는 모달창)
3. SelectRegion.js (지역을 선택하는 모달창)
4. SelectDate.js (날짜를 선택하는 모달창)
5. SelectVisitors.js (인원을 선택하는 모달창)
Modal창 구현의 기본 원리는 true면 보이게! false면 안보이게!
//SearchClickedModal.js
//부모
const [modalCondition, setModalCondition] = useState(false);
//모달창은 초기화면에서는 보이지 않기 때문에 초깃값은 false
const closeRegionModal = () => {
setModalCondition(false)
//또는
setModalCondition(!modalCondition)
}
//Modal창의 상태가 true라면 보여지고 fasle라면 보이지 않게 한다.
return
//삼항 연산자를 작성하여 modalCondition이 true일 때 SelectRegion이 보여지도록
{modalCondition && (
<SelectRegion
closeRegionModal={() => setModalCondition(!modalCondition)}/>
)}
//모달창 컴포넌트에 closeRegionModal 함수도 넘겨주기
생각보다 간단한 Modal 창 구현 딱 2줄이면 충분!
//SelectRegion.js
//자식
const SelectRegion = ({ closeRegionModal }) => {
//모달창 닫히는 함수 props로 가져오기
return (
<>
<ModalOutside onClick={closeRegionModal} />
//바깥 영역 클릭시 함수 작동하여 모달창 닫힘
<SelectWrapper onClick={e => e.stopPropagation()}>
//모달창 자체를 클릭하면 모달창이 닫히지 않게!
<JejuCity onClick={jejuClicked}>제주시</JejuCity>
<SeogwipoCity onClick={seogwipoClicked}>서귀포시</SeogwipoCity>
</SelectWrapper>
</>
);
};
<ModalOutside/>
는 모달창 바깥 부분이다.
<SelectWrapper>
는 모달창 자체이다.
모달창 바깥 부분인 <ModalOutside/>
의 background-color
는 어두운 색으로 opacity
를 부여했다.
onClick
으로 {closeModal}
을 할당했는데 이는 모달의 state
를 fasle
로 업데이트하는 함수이다.
<ModalOutside/>
의 자식으로 <SelectWrapper>
가 있는데 이는 모달창 자체이므로 부모처럼 클릭이 되었다고 해서 꺼지면 안된다.
따라서 onClick={e => e.stopPropagation()}
을 작성하면 현재 이벤트가 캡처링/버블링 단계에서 더 이상 전파되지 않도록 방지한다. 즉 , 부모의 이벤트가 자식의 이벤트에 영향을 끼치지 않게 하는 메서드이다. 참고 문서
모달창 구현은 금방 끝나지만 modal창 안에 있는 내용의 state
와 업데이트를 어떻게 props
로 넘겨줘야 하는지 전체적인 구조를 항상 생각해야했다.
모든 컴포넌트들이 부모 자식 관계였다면 쉬웠겠지만 어떤 모달창은 형제 관계이지만 state
를 공유해야 하는 상황, 어떤 모달창은 자식 컴포넌트에서 부모 컴포넌트에 state
를 넘겨줘야 하는 말도 안되는 상황(props는 무조건 단방향 위에서 아래로)
이 모든 상황을 생각하면서 props
의 미로에 빠졌다...모달창의 지옥에 빠졌다.
결국은 최상단인 Nav.js에서 모든 state
와 함수를 작성하고 하나씩 넘겨주기로 했다.
Redux나 Recoil은 좀 더 복잡한 구조일 때 사용해보기로! 꼭 필요하지 않는 이상 사용하는 것을 권장하지 않는다.
const Nav = () => {
//자식 컴포넌트에 넘겨줄 함수들 최상위에 선언 완료
const [searchModal, setSearchModal] = useState(false);
const [whereModal, setWhereModal] = useState(false);
const [whenModal, setWhenModal] = useState(false);
const [visitorsModal, setVisitorsModal] = useState(false);
const [clickedCity, setClickedCity] = useState('여행지 검색');
const [adultCount, setAdultCount] = useState(0);
const [childrenCount, setChildrenCount] = useState(0);
const [infantCount, setInfantCount] = useState(0);
const [petCount, setPetCount] = useState(0);
const closeAllModals = () => {
searchModal && setSearchModal(false);
whereModal && setWhereModal(!whereModal);
whenModal && setWhenModal(!whenModal);
visitorsModal && setVisitorsModal(!visitorsModal);
};
const addAdultCount = () => {
adultCount >= 0 && setAdultCount(adultCount + 1);
};
const removeAdultCount = () => {
adultCount && setAdultCount(adultCount - 1);
};
const addChildrenCount = () => {
childrenCount >= 0 && setChildrenCount(childrenCount + 1);
};
const removeChildrenCount = () => {
childrenCount && setChildrenCount(childrenCount - 1);
};
const addInfantCount = () => {
infantCount < 6 && setInfantCount(infantCount + 1);
};
const removeInfantCount = () => {
infantCount && setInfantCount(infantCount - 1);
};
const addPetCount = () => {
petCount < 6 && setPetCount(petCount + 1);
};
const removePetCount = () => {
petCount && setPetCount(petCount - 1);
};
const resetSelection = () => {
setAdultCount(0);
setChildrenCount(0);
setInfantCount(0);
setPetCount(0);
};
const [startDate, setStartDate] = useState(null);
const [endDate, setEndDate] = useState(null);
const onChange = dates => {
const [start, end] = dates;
setStartDate(start);
setEndDate(end);
};
const checkInDate =
startDate != null &&
startDate
.toLocaleString()
.slice(0, -13)
.split(' ')
.join('-')
.replace(/\./g, '');
const checkOutDate =
endDate != null &&
endDate
.toLocaleString()
.slice(0, -13)
.split(' ')
.join('-')
.replace(/\./g, '');
//자식 컴포넌트에 state 및 함수 props로 넘겨주기
return (
<div>
<Navigation primary={!searchModal}>
<Wrapper>
<Logo>
<ImageBox>
<Link to="/">
<Image src="/images/Logo.png" />
</Link>
</ImageBox>
</Logo>
<Search searchModal={searchModal} setSearchModal={setSearchModal} />
<LogIn closeAllModals={closeAllModals} />
</Wrapper>
{searchModal && (
<SearchClickedModal
closeAllModals={closeAllModals}
whereModal={whereModal}
setWhereModal={setWhereModal}
whenModal={whenModal}
setWhenModal={setWhenModal}
visitorsModal={visitorsModal}
setVisitorsModal={setVisitorsModal}
clickedCity={clickedCity}
adultCount={adultCount}
childrenCount={childrenCount}
infantCount={infantCount}
petCount={petCount}
checkInDate={checkInDate}
checkOutDate={checkOutDate}
/>
)}
</Navigation>
{whereModal && (
<SelectRegion
closeWhereModal={() => setWhereModal(!whereModal)}
setClickedCity={setClickedCity}
/>
)}
{whenModal && (
<SelectDate
onChange={onChange}
startDate={startDate}
endDate={endDate}
closeWhenModal={() => setWhenModal(!whenModal)}
/>
)}
{visitorsModal && (
<SelectVisitors
closeVisitorsModal={() => setVisitorsModal(!visitorsModal)}
adultCount={adultCount}
setAdultCount={setAdultCount}
childrenCount={childrenCount}
setChildrenCount={setChildrenCount}
infantCount={infantCount}
setInfantCount={setInfantCount}
petCount={petCount}
setPetCount={setPetCount}
addAdultCount={addAdultCount}
removeAdultCount={removeAdultCount}
addChildrenCount={addChildrenCount}
removeChildrenCount={removeChildrenCount}
addInfantCount={addInfantCount}
removeInfantCount={removeInfantCount}
addPetCount={addPetCount}
removePetCount={removePetCount}
resetSelection={resetSelection}
/>
)}
</div>
);
};
이번 프로젝트에서 가장 오랜시간동안 나를 괴롭혔던 복잡한 모달창 관계와 state 관리... 매순간 포기하고 싶었고 팀원들에게 구현 못할 것 같다고 말했지만 결국은 해냈다. 포기하지 마라는 말 별로 좋아하지 않았는데.. 뭐 포기하면 어때? 였는데 포기하지 않으니깐 그 댓가로 엄청난 성장을 받게 된다. 코딩 실력이 눈에 띄게 늘었다는 것이 아니라 뭔든지 할 수 있을 것 같은 자신감과 또 다른 큰 벽을 만나도 도전할 수 있는 정신적인 성장
뭐야...갑자기 코딩에서 인생을 배운건가...
여튼 아무리 복잡하고 어려워도 왜 안돼???를 반복하며 하나하나 풀어나가자!!