기존 모달 사용 방식은 Context안에 children을 보내서 createPortal로 띄우는 방식이었다. 그런데 이 방식은 모달이 하나 뜨면 다른 모달이 꺼져버린다는 것이 문제가 되었다. 모달 위에 확인 모달을 띄워야 할 때가 있는데 이 경우에는 dom 구조 바깥에 뜨는 modal의 z-index를 조정한다고 해결이 되지 않아서 변경하기로 마음을 먹었다.
//ModalProvider.js
export default function ModalProvider({ children, selector }: Props) {
const ref = useRef<any>(null)
const [mounted, setMounted] = useState(false)
const [modal, setModal] = useState<ReactNode>(null)
useEffect(() => {
ref.current = document.getElementById(selector)
setMounted(true)
}, [selector])
const handleClickOverlay = (event: React.MouseEvent<HTMLDivElement>) => {
if (event.currentTarget !== event.target) return
setModal(null)
}
const renderModal = () => {
return modal ? (
<Overlay
onClick={e => {
if (clickable) handleClickOverlay(e)
return
}}
role='presentation'
>
{modal}
</Overlay>
) : null
}
return (
<ModalContext.Provider value={{ setModal, setClickable, setScrollable }}>
{mounted ? createPortal(renderModal(), ref.current) : null}
{children}
</ModalContext.Provider>
)
}
import { createSlice } from '@reduxjs/toolkit'
import { ReactNode } from 'react'
export type ModalType = {
type: string
children: ReactNode
isCloseable?: boolean
}
const initialState: Array<ModalType> = []
export const modalSlice = createSlice({
name: 'modal',
initialState,
reducers: {
openModal: (state, action) => {
//type은 모달을 구분 짓는 이름, children은 띄울 modal
const { type, children } = action.payload
return state.concat({ type, children })
},
closeModal: (state, action) => {
state.pop()
},
},
})
export const { openModal, closeModal } = modalSlice.actions
export default modalSlice.reducer
모달이 쌓이면 이런식으로 나오게 된다.
그리고 이걸 hook으로 만들어서 어디서나 편하게 가져다 쓸 수 있게 만들었다.
import { ModalType, closeModal, openModal } from '@src/store/modal'
import { useAppDispatch } from './useRedux'
function useModal() {
const dispatch = useAppDispatch()
const handleOpenModal = ({ type, children, isCloseable }: ModalType) => {
dispatch(openModal({ type, children, isCloseable }))
}
const handleCloseModal = (type: string) => {
dispatch(closeModal(type))
}
return { openModal: handleOpenModal, closeModal: handleCloseModal }
}
export default useModal
openModal({ type: '' , children : <Modal /> })
//type에는 unique한 이름, children에는 띄우고 싶은 컴포넌트
closeModal('')
//openMdoal에서 type에 적는 unique한 이름
이렇게 모달 위에 모달이 잘 뜨게 된다!
참고 : https://velog.io/@rkio/React-ReactDOM.createPortal
https://ko.reactjs.org/docs/portals.html
https://jeonghwan-kim.github.io/2022/06/02/react-portal