ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React] Modal 모듈화및 Portal
    Web/Front-End 2021. 3. 11. 16:40

    사이트를 개발 하다보니 팝업및 모달창을 뜨게 해야할 일이 꽤 여러곳에 있었다.

     

    해당 모달들은 유니크한 폼을 가진 것도 있지만 대부분 버튼 하나 두개로 이루어진 베이직한 모달들이였는데

    이런 모달들을 사용할때 매번 새로운 모달을 생성하기보단 특정 값들만 넘겨주어 기본 폼에서 벗어나지않게 공통 모듈로 빼보려고 했다.

     

    공통 모듈로 빼보려고 하니 모달 자체는 언제 어디서나 떠야 하는 성질을 가지고있기에 Portal도 사용하도록 했다.

     

    먼저 public/index.html에 모달이 뜰 영역을 정의해준다.

    나는 해당 모달 영역을 이용할 ModalPortal.js 파일을 생성하였다.

    const ModalPortal = ({ children }) => {
    
    	const el = document.getElementById('modal')
    
    	return ReactDOM.createPortal({children}, el)
    }
    
    export default ModalPortal
    

    ModalPortal 은 기본적으로 위 와 같은 형태를 가지고 있는데, 코드를 만지작 거리던 중 당연하게도(?) 해당 포탈은 커스터마이징이 가능한걸 알게되었다.

    퍼블리싱 받은 파일에 맞게 아래와 같이 커스터마이징 하였고, 직접 작성할땐 본인에 맞게 커스텀하는게 중요해 보인다.

    모든 모달에 공통 요소를 미리 정의해주면 불필요한 중복 코드작성을 피할 수 있다.

     

    이제 모달포탈을 생성했으니 모달을 정의해보러 가보자.

     

    프로젝트의 모달관련 디렉토리 구조를 위와 같이 잡아놓았다.

    미리 모달 컴포넌트들은 Component 디렉토리에 작성을 하고

    모달이 필요한 다른 컴포넌트에서 getModal.js 안에 메서드를 호출 하면

    getModal.js에서 데이터를 넘겨줘서 컴포넌트를 반환하는 형식으로 작성하였다.

     

    import React from 'react'
    
    const NoticeModal = (props) => {
    	return (
    		<div className="wrap-popup popup-active" data-popup="popupGrade">
    			<div className="popup-area">
    				<div className="popup-title">
    					{props.title}
    				</div>
    				<div className="popup-content">
    					{props.body}
    				</div>
    				<div className="popup-btn-area">
    					<button type="button" className="btn btn-text" onClick={() => props.btnHandler()}>
    						<span>{props.btn}</span>
    					</button>
    				</div>
    			</div>
    		</div>
    	)
    }
    
    export default NoticeModal
    

    해당코드는 NoticeModal.jsx 로 버튼이 한개인 모달창을 컴포넌트화 시켜 재사용을 하려고 한것이다.

     

    export default {
    	getDeviceDetail (param1, param2, isShowing) {
    		...
    	},
    
    	getUserGrade (isShowing) {
    		...
              const modal = {
                  title: <h2 className="title-text heading3"><label htmlFor="search-group-popup">검색</label></h2>,
                  body: bodySearchSpace,
                  btn: '취소',
                  btnHandler () {
                      isShowing(false)
                  }
              }
              ...
              return (
                  <ModalPortal>
                      <NoticeModal title={modal.title}
                                   body={modal.body}
                                   btn={modal.btn}
                                   btnHandler={modal.btnHandler}
                      />
                  </ModalPortal>
              )
    	}
    }
    

    getModal.js 파일 내부이다.  getUserGrade 메서드를 정의하여 NoticeModal에 넘어갈 데이터들을 정리해준다.

    정리해 준 후 props로 컴포넌트에 넘겨주면되는데

     

    이때 ModalPortal로 감싸놔야 미리 정의해둔 ModalPortal 영역에 띄워줄 수 있다.

     

    이제 모달을 띄울 부모 컴포넌트에선

    const Component = () => {
    
    	return (
        	<>
            	<ParentComponent />
                {isShowing ? getModal.getGroupSearch (setIsShowing) : null}
            </>
        )
    }

    와 같이 선언을 해준다. 이렇게 하면 내부적으로 isShowing이라는 State를 true로 바꿔줄 때 모달이 뜨게된다.

     

    여기까지하면 모달이 뜨고 끝.. 인줄 알았으나,

     

    부모 컴포넌트의 스크롤이 모달이 뜬 상태에서도 동작하는 이슈를 발견하였고 다시 머리를 싸매기 시작했다.

    해당 이벤트는 css를 body태그에 추가하는 방식으로 하기로하였고, 퍼블리싱 레이아웃에 맞춰 구현한것이므로 본인에게 맞는 스타일 속성을 찾길 바란다.

     

    const ModalPortal = ({ children }) => {
    
    	useEffect(() => {
    		document.body.style.cssText = `overflow: hidden; top: -${window.scrollY}px`
    		return () => {
    			const scrollY = document.body.style.top
    			document.body.style.cssText = `top: "";`
    			window.scrollTo(0, parseInt(scrollY || '0') * -1)
    		}
    	}, [])
    
    	const el = document.getElementById('modal')
    
    	return ReactDOM.createPortal(<div className='modalspace'>
    		<span className='dimmed'>
    			{children}
    		</span>
    	</div>, el)
    }

    모든 모달창에서 공통적인 기능으로 되어야 한다 생각하여 ModalPortal 안에 구현해주었다.

    댓글

Designed by Tistory.