ํ ์ค์ useFunnel์ ๋ณด๊ณ ์ค์ ํ๋ก์ ํธ์ ํ์ด์ง ํ๋ฆ์ ๋ณ๊ฒฝ์ฑ ์ข๊ฒ ์ ์ฉํด ๋ณธ ๊ฒฝํ์ ๋๋ค.
ํ๋ก์ ํธ๊ฐ ์ปค์ง์๋ก ์ ์ ๋ ๋ง์ modal์ด ์๊ฒจ๋๊ธฐ ์์ํ์ต๋๋ค. ๊ธฐ์กด์ ์กด์ฌํ๋ 10๊ฐ ํ์ผ์ด ์์๊ณ ์ถ๊ฐ๋ก ์ฌ์ฉํ๋ Modal๊น์ง ๋ฃ๋๋ค๋ฉด ๋งค์ฐ ๋ง์ modal์ด ํ๋ก์ ํธ ๋ด์ ์ฌ์ฉ๋๊ณ ์๋ ๊ฒ์ด์์ฃ .
๋ณดํต modal์ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ํ๋์ ํ์ด์ง ๋ด๋ถ์ state๋ก modal์ on off๋ฅผ ๊ด๋ฆฌํ๋ ์์ผ๋ก ์๋ํฉ๋๋ค. ์ ํฌ ํ๋ก์ ํธ ๋ํ ์ด๋ ๊ฒ ๊ด๋ฆฌํ๊ณ ์์์ต๋๋ค.
const [isOpen,setIsOpen] = useState(false);
const closeModal = ()=>{
setIsOpen(false);
}
const openModal = ()=>{
setIsOpen(true)
}
return (
<Modal1/>
<Modal2/>
...
)
ํ์ง๋ง ์ด ํ์ด์ง๋ ์ปดํฌ๋ํธ ์์ ์๋ modal ๋์ ์ฝ๊ฐ์ ๋ฌธ์ ๊ฐ ์๋๋ฐ์. ์ปดํฌ๋ํธ๋ ํ์ด์ง์ ์ข ์์ ์ด๋ค ๋ณด๋ modal์ ๋ ์ด์์์ด ๋ฐ์ css์ ์ํฅ์ ๋ฐ๋ ๊ฒฝ์ฐ๋ ์ข ์ข ์๊น๋๋ค.
์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ๋ฐฉ๋ฒ์ด react์ createPortal
์
๋๋ค. ํด๋น dom์ ํน์ ๊ตฌ์ญ์ ๋ ๋๋ง์ํค๋ ๋ฐฉ๋ฒ์
๋๋ค.
๋ค์์ root element ๋ฐ๋ก ๋ฐ์ modal์ ๋ ๋๋ง ์ํค๋ ์ฝ๋์ ๋๋ค.
const Modal = ({ children, isOpen }) => {
if (!isOpen) {
return null;
}
return ReactDOM.createPortal(
<div>
<div>{children}</div>
<div></div>
</div>,
document.getElementById("root")
);
};
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const closeModal = () => {
setIsOpen(false);
};
const openModal = () => {
setIsOpen(true);
};
return (
<div>
<button onClick={openModal}>๋ชจ๋ฌ ์ด๊ธฐ</button>
<Modal isOpen={isModalOpen}>
<h1>๋ชจ๋ฌ ์ ๋ชฉ</h1>
<p>์ฌ๊ธฐ์ ๋ด์ฉ์ ์ฑ์๋ฃ์ผ์ธ์.</p>
<button onClick={closeModal}>๋ซ๊ธฐ</button>
</Modal>
</div>
);
};
ํ์ง๋ง ์ด๊ฒ์ modal์ ๋ ๋๋ง ์์น๋ฅผ ๋ฐ๊พธ์ด์ค๋ค์ผ๋ฟ ์ฝ๋ ๊ตฌ์กฐ์์ผ๋ก๋ ๋ฐ๋๊ฒ ์์ต๋๋ค. ์ฌ์ ํ modal์ on off๋ฅผ ๊ด๋ฆฌํ๋ state๋ ํด๋น ํ์ด์ง๋ ์ปดํฌ๋ํธ์ ์กด์ฌํ๊ณ ์์ต๋๋ค.
์ผ๋จ์ ์ closeModal openModal์ด modal์ ์ฌ์ฉํ๋ ํ์ผ๋ง๋ค ๋ชจ๋ ์ค๋ณตํด์ ์์ฑํด์ผ ํ๋ค๋ ๋ถํธํ ์ ์ด ์์ต๋๋ค. ๊ทธ๋์ ๊ฐ๋จํ๊ฒ ์ ๊ฒ์ useModal hook์ผ๋ก ๋ง๋ค์ด ๋ณด๊ณ ์ ํ์์ต๋๋ค.
const useModal = (initialState = false) => {
const [isOpen, setIsOpen] = useState(initialState);
const openModal = () => setIsOpen(true);
const closeModal = () => setIsOpen(false);
return { isOpen, openModal, closeModal };
};
export default useModal;
const App = () => {
const { isModalOpen, closeModal, openModal } = useModal(false);
return (
<div>
<button onClick={openModal}>๋ชจ๋ฌ ์ด๊ธฐ</button>
<Modal isOpen={isModalOpen}>
<h1>๋ชจ๋ฌ ์ ๋ชฉ</h1>
<p>์ฌ๊ธฐ์ ๋ด์ฉ์ ์ฑ์๋ฃ์ผ์ธ์.</p>
<button onClick={closeModal}>๋ซ๊ธฐ</button>
</Modal>
</div>
);
};
ํ์ง๋ง ์ค๋ณต๋๋ ์ฝ๋๋ ์ค์ผ ์๋ ์์ด๋ ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ ๋ ํด๊ฒฐํ์ง ๋ชปํ์์ต๋๋ค. ๋ฐ๋ก modal์ ํ์ด์ง๋ ์ปดํฌ๋ํธ์ ์ข ์์ ์ด์ง ์์๋ฐ JSX๋ ๊ฒฐ๊ตญ ํด๋น ํ์ด์ง๋ ์ปดํฌ๋ํธ์ JSX์ ์์ด์ผ ํ๋ค๋ ์ ์ ๋๋ค.
์ด๋ฐ ์์ ์ฝ๋๋ ๊ฒฐ๊ตญ ์ฝ๋๊ฐ ๊ธธ์ด์ก์ ๋ ๋ค์๊ณผ ๊ฐ์ ๊ตฌ์กฐ๊ฐ ๋ฉ๋๋ค.
์ด๋ฐ ๊ตฌ์กฐ๋ 2๊ฐ์ง ์ด์ํ๋ค๊ณ ์๊ฐํ๋ ์ ์ด ์์๋๋ฐ์. ์ฒซ ๋ฒ์งธ๋ ํ์ด์ง๋ ์ปดํฌ๋ํธ์์ ๊ด์ฌ์ด ์๋ ๊ฒ์ ์ด modal์ ์ด์ด์ผ ํ ์ง ๋ซ์์ผ ํ ์ง์ ๋ํ ์ฑ ์๋ง ํ์ํ๋ค ์๊ฐํ์ต๋๋ค. ๋ ๋ฒ์งธ๋ก๋ createPortal์ ์ฌ์ฉํ๋ฉด dom ์์ฒด๋ root ๋ฐ๋ก ๋ฐ์์ ๋ ๋๋ง ๋์ง๋ง ์ค์ jsx ์ฐ๊ฒฐ๋ถ๋ ํ์ด์ง๋ ์ปดํฌ๋ํธ์ฌ์ผ ํ๋ค๋ ์ฌ์ค์ด ์ด์ํ์ต๋๋ค.
๊ทธ๋์ ์๊ฐํ ๊ฒ modal์ JSX๊ฐ ๊ตณ์ด ์ฌ๊ธฐ์ ์กด์ฌํ์ฌ์ผ ํ๋๊ฐ์์ต๋๋ค. ์ด๊ฑธ hook์ด ๊ด๋ฆฌํ๋ฉด ๋์ง ์์๊น ์๊ฐํ์๊ณ ๋ง์นจ ์ ์ญ ์ํ๋ฅผ ์ฌ์ฉํ๊ณ ์์ด ์ด๋ฅผ ์ด์ฉํด ๋ณด๊ณ ์ ํ์์ต๋๋ค.
์ด๋ฐ ์์ผ๋ก useModal์ ์ด์ฉํด์ ์ ์ญ ์ํ์ modal ์ปดํฌ๋ํธ ์์ฒด๋ฅผ ์ํ๋ก ์ฌ๋ ค๋๊ณ root ๋ฐ๋ก ์๋์์ ๋ ๋๋ง ์์ผ์ฃผ๋ ค ํ์์ต๋๋ค.
๊ทธ๋ฌ๋ฉด useModal hook์ ๋ค์๊ณผ ๊ฐ์ ๊ตฌ์กฐ๋ก ์ธ ์ ์์ฃ
const { isOpen, openModal, closeModal } = useModal(() => {
return (
<AnswerSelectionModal
workbookId={workbookId}
question={question}
closeModal={closeModal}
/>
);
});
์ด๋ฐ ๋ฐฉ์์ผ๋ก ๊ตฌํํ ๋๋ ๋ค์๊ณผ ๊ฐ์ ๋ถ๋ถ์ ๊ณ ๋ คํด ์ฃผ์ด์ผ ํฉ๋๋ค.
export const modalState = atom<{ id: string; element: React.FC }[]>({
key: 'modalState',
default: [],
});
๋จผ์ ์ ํฌ ํ๋ก์ ํธ๋ recoil์ ์ฌ์ฉํ๊ณ ์์์ผ๋ฏ๋ก atom์ ์ด์ฉํ ์ ์ญ ์ํ๋ฅผ ์ ์ํด์ผ ํฉ๋๋ค. ๋ฐ๋ผ์ ๋ค์๊ณผ ๊ฐ์ด ์ ์๋ฅผ ํ์์ฃ . ๊ฐ์ id์ functional component๋ฅผ ์ ์ญ์ผ๋ก ๋ฐ๊ณ ์์ต๋๋ค.
id๋ฅผ ์ด์ฉํ์ฌ modal์ ์๋ณํ ์ ์๊ณ ๋ฐฐ์ด๋ก ๋ง๋ค์ด modal์ด ์ฌ๋ฌ ๊ฐ๋ฅผ openํด๋ ์ค์ฒฉ์ผ๋ก modal์ ์ฌ์ฉํ ์ ์๋๋ก ํ์์ต๋๋ค.
useModal์ ๊ตฌํ๋ถ๋ฅผ ๋ณด๊ฒ ์ต๋๋ค.
const isArrEmpty = (arr: unknown[]) => arr.length === 0;
const useModal = (component: React.FC) => {
const [modalElements, setModal] = useRecoilState(modalState);
// ์ ์ญ ์ํ์ element ์์ฒด๋ฅผ ์ํ๋ก ์ ์ฅํ๋ค.
const [isOpen, setIsOpen] = useState(false);
// ํด๋น ์ปดํฌ๋ํธ๊ฐ ์ด๋ ธ๋์ง ์์ด๋ ธ๋์ง ์๋ ค์ฃผ๋ ์ํ
const id = useId();
// ์ปดํฌ๋ํธ์ id๋ฅผ ๋ถ์ฌํด์ ์๋ณํ๋ค.
const modalComponent = useMemo(() => component, []);
const openModal = useCallback(() => {
setIsOpen(true);
setModal((pre) => [...pre, { id: id, element: modalComponent }]);
// modal์ ์ ์ญ์ํ์ ์ถ๊ฐํ๋ค
document.body.style.overflow = "hidden";
// modal์ด open๋๋ฉด ๋ฐฐ๊ฒฝ์ ์คํฌ๋กค์ ๋ง์์ผํจ
}, []);
const closeModal = useCallback(() => {
setIsOpen(false);
setModal((pre) => pre.filter((c) => c.id !== id));
// modal์ ์ ์ญ์ํ์์ ์ ๊ฑฐํ๋ค.
if (isArrEmpty(modalElements)) document.body.style.overflow = "unset";
// modal์ด ๋ชจ๋ ๊บผ์ง๋ฉด ๋ฐฐ๊ฒฝ์ ์คํฌ๋กค์ด ๊ฐ๋ฅํด์ผํจ
}, []);
return { isOpen, openModal, closeModal };
};
export default useModal;
useModal์ ์ ์ญ ์ํ์ ํด๋น modal ์ปดํฌ๋ํธ๋ค์ ์ถ๊ฐ ๋๋ ์ ๊ฑฐํด์ modal์ด ์ด๋ ธ๋์ง ๋ซํ๋์ง๋ฅผ ๊ด๋ฆฌํด ์ฃผ๋ hook์ ๋๋ค. ์ฒซ ๋ฒ์งธ ๊ณ ๋ ค์ฌํญ์ด์ฃ
์ ๊ทธ๋ฌ๋ฉด ์ด์ ์ ์ญ ์ํ์ธ modal ์ปดํฌ๋ํธ๋ฅผ ๋๋๋ง ํด์ modal์ด root ๋ฐ๋ก ๋ฐ์์ ์ด๋ฆฐ๋ค๋ ๋ ๋ฒ์งธ ๊ณ ๋ ค ์ฌํญ์ ๋ณด๊ฒ ์ต๋๋ค. ์ ๋ ์ด๋ฅผ ModalProvider๋ก ํด๊ฒฐํ๋๋ฐ์
import { modalState } from "@atoms/modal";
import { useRecoilState } from "recoil";
const ModalProvider = () => {
const [state] = useRecoilState(modalState);
return (
<>
{state.map(({ id, element }) => {
return <Component key={id} component={element} />;
})}
</>
);
};
const Component = ({ component, ...rest }: { component: React.FC }) => {
return component({ ...rest });
};
export default ModalProvider;
์ ์ญ ์ํ๋ฅผ ๊ฐ์ ธ์์ ๋จ์ํ๊ฒ ๋ ๋๋ง ํด์ฃผ๊ณ ์๋ ์ญํ ์ ํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ๋ฐฐ์ด๋ก ๋์ด ์์ด ๋ฐฐ์ด์์ ๋ค ์์๋ก ์๋ modal์ด ์์ ๋งฅ๋ฝ ๋๋ฌธ์ ์ด์ modal๋ณด๋ค ๋ ์์ ์กด์ฌํ๊ฒ ๋์ด์ ์ค์ฒฉ modal์ ์ด๋ฐ ๋จ์ ๋ฐฐ์ด๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
ํด๋น ModalProvider์ provider๋ค์ ๊ด๋ฆฌํ๊ณ ์๋ App ์ปดํฌ๋ํธ์์ ์คํ์ด ๋๊ณ ์๋๋ฐ์
...
<RecoilRoot>
<ThemeProvider theme={theme}>
<Global styles={_global} />
<AppRouter queryClient={queryClient} />
<GlobalSVGProvider />
<ModalProvider />
</ThemeProvider>
</RecoilRoot>
...
์ด๋ฌ๋ฉด router๋ณด๋ค๋ ๋ฐ๊นฅ ์์ญ์ด๊ณ root๋ณด๋ค๋ ์๋ ์์ญ์ด๋ ๋ค์๊ณผ ๊ฐ์ ๊ณณ์์ modal์ด ์ถ๊ฐ๊ฐ ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
๊ฒฐ๊ตญ ํด๋น ์ ์ญ state์ ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ๋ฉด ๊ทธ ์ฆ์ root ํ๋จ ๋ถ์ ๋ฐ๋ก ๋ ๋๋ง ๋๊ณ ์๋ ๊ฑธ ๋ณผ ์ ์์ต๋๋ค.
modal์ ์ ์ดํ๋ ๋ถ๋ถ์ hook์ผ๋ก ๋ชจ์ผ๋ฉด์ ์์ง๋๋ฅผ ๋์ผ ์ ์๊ณ ํ์คํ ์ฑ ์์ ์ฃผ์๋๋ฐ์. ์ ๊ฐ ์ด hook์ ์ฌ์ฉํ๋ฉด์ ๊ฐ์ฅ ํฌ๊ฒ ์ฅ์ ์ผ๋ก ๋๋ ๊ฒ์ modal์ ์ข ๋ฅ๊ฐ ๋ง์ ํ๋ก์ ํธ์ ์ ์ฉํ๊ฒ ์ฌ์ฉ๋์๋ค๊ณ ์๊ฐํฉ๋๋ค. ๊ฐ๊ฐ์ modal์ ํ์ํ ๋ฐ์ดํฐ๊ฐ ๋ง์๋ฐ modal์ ๋ฑ๋กํ๋ ์์น๊ฐ ์์ ๋ก์ฐ๋ ์ ํํ ์ ์๋ ์ ํ์ง๊ฐ ๋ง์์ง ๊ฒ์ด์ฃ .
ํนํ ๋ถํ์ํ ์ฝ๋๋ค์ด ์ค์ด๋ฉด์ ๋ ์ด์ ๊ฐ๋ฐ์๋ modal์ ์ํ๋ฅผ ์ง์ ํ๋ state modal์ ๋ ๋๋ง ์์น ๋ฑ์ ์ ํ์๊ฐ ์์ด์ก์ต๋๋ค. ๊ทธ๋ฅ modal์ ์ ๋์์ธํ๊ณ ์ ๋์์ํค๊ธฐ๋ง ํ๋๋ฐ ์ง์ค์ ํ๋ฉด ๋ฉ๋๋ค.