ํฐ์คํ ๋ฆฌ ๋ทฐ
Issue
ํ์ฌ ํ๋ก์ ํธ์์ ๋๋ถ๋ถ์ ์ฌ์ด๋ ์ด๋ฒคํธ์ ๋ํ ๋ด์ฉ์ Modal ์ฐฝ์ด ๋์์ ธ ํ๋ฉด์ ๋๋๋ง๋๋ค.
์ด๋ฅผ ์ํด Modal ์ฐฝ์ ๋ํ Presentation Component์ธ BodyBlackout ์ฝ๋๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ์๋ค.
mport { useDispatch } from "react-redux";
import styled from "styled-components";
const BodyBlackout = ({
modalSort,
setAuthModalVisible,
isVisibleAuthModal,
}) => {
const dispatch = useDispatch();
if (isVisibleAuthModal)
document.querySelector("body").style.setProperty("overflow", "hidden");
return (
<BodyBlackoutStyle
onClick={() => {
dispatch(setAuthModalVisible(modalSort));
}}
/>
);
};
const BodyBlackoutStyle = styled.div`
position: absolute;
z-index: 1010;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.65);
cursor: pointer;
`;
export default BodyBlackout;
ํด๋น BodyBlackout ์ปดํฌ๋ํธ์ ๋๊ฒจ์ง props๊ฐ์ ํตํด ๊ฐ ์ปดํฌ๋ํธ์ ์ค์ ๊ฐ์ ๋ง๊ฒ Modal ์ฐฝ์ด ๋์์ง๋๋ก ํ์๋ค.
์๋ฅผ ๋ค์ด ์๋์ ๊ฐ์ด Register Container Component ๋ด์์ react-redux store ๋ด์์ ํ์ฌ Modal ์ฐฝ์ด ํ๋ฉด์ ๋ณด์ฌ์ง๋์ง์ ๋ํ ์ฌ๋ถ๋ฅผ ์ ์ฅํ๋ isVisibleAuthModal state ๊ฐ์ ์ค์ ํ์๊ณ ์ด๋ฅผ BodyBlackout Component์ props๋ก ์ ๋ฌํ๋ ๋ก์ง์ผ๋ก Modal ์ฐฝ์ ๋๋๋ง ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋๋ก ์ฝ๋๋ฅผ ์์ฑํ์๋ค.
์ก์ ์ด ๋ฐ์ํ ๊ฒฝ์ฐ Modal ์ฐฝ์ด ๋ณด์ฌ์ง๋์ง ์ฌ๋ถ๋ฅผ ์ค์ ํด์ฃผ๋ ์ก์ ์์ฑ ํจ์ setAuthModalVisible๋ฅผ ๋ง๋ค์ด์ฃผ๊ณ ํด๋น ์ก์ ์์ฑ ํจ์์ ์ธ์๋ก๋ ํ์ฌ ํ์ฑํ๋์ด์ผ ํ form์ ๋ฐ์ ์ ์๋๋ก ์ฝ๋๋ฅผ ์์ฑํ์๋ค.
( ... )
const SET_AUTHMODALVISIBLE = "auth/SET_AUTHMODALVISIBLE";
( ... )
export const setAuthModalVisible = createAction(
SET_AUTHMODALVISIBLE,
form => form
);
const initialState = {
login: {
( ... )
isVisibleAuthModal: false,
},
register: {
( ... )
isVisibleAuthModal: false,
is_receive_email: false,
},
};
const auth = handleActions(
{
( ... )
[SET_AUTHMODALVISIBLE]: (state, { payload: form }) =>
produce(state, draft => {
draft[form].isVisibleAuthModal = !state[form].isVisibleAuthModal;
}),
},
initialState
);
export default auth;
์ด๋ ๊ฒ auth module์์ ์ค์ ํ isVisibleAuthModal๊ณผ setAuthModalVisible๋ฅผ Register Container Component ๋ด์์ ๊ฐ์ ธ ์ Auth Presentation Component ๋ด์์ ์ก์ ์ด ๋ฐ์ํ ๊ฒฝ์ฐ์ ํด๋น ์ก์ ํจ์๋ฅผ ํตํด redux-store์ ์๋ isAuthModalVisible state๊ฐ์ ๋ณ๊ฒฝํ๋๋ก ์ฝ๋๋ฅผ ์์ฑํ์๋ค.
import React, { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
setAuthModalVisible,
} from "../../modules/auth";
import BodyBlackout from "../../components/common/BodyBlackout";
const RegisterFormContainer = () => {
const dispatch = useDispatch();
const isVisibleAuthModal = useSelector(
({ auth }) => auth.register.isVisibleAuthModal
);
( ... )
if (isVisibleAuthModal) {
return (
<>
<BodyBlackout
modalSort="register"
setAuthModalVisible={setAuthModalVisible}
isVisibleAuthModal={isVisibleAuthModal}
/>
<AuthTemplate>
<RegisterForm
form={form}
checkCurrentAdvertiseAccess={checkCurrentAdvertiseAccess}
isCorrectPasswordPattern={isCorrectPasswordPattern}
authError={authError}
changeRegisterInputValue={changeRegisterInputValue}
inputValueDuplicationCheck={inputValueDuplicationCheck}
sendToEmailForVerificationCode={sendToEmailForVerificationCode}
signup={signup}
/>
</AuthTemplate>
</>
);
} else return null;
};
export default RegisterFormContainer;
ํ์ง๋ง ์ด๋ ๊ฒ ์ฝ๋๋ฅผ ์์ฑํ๋ค ๋ณด๋, ๊ธฐ๋ฅ ์์๋ ๋ฌธ์ ๋ ์์ง๋ง Modal ์ฐฝ ๊ธฐ๋ฅ์ด ํ์ํ ๋ชจ๋ ์ปดํฌ๋ํธ์์ modalVisible state๊ฐ์ store์ ์ผ์ผํ ์ ์ฅํด๋์ด์ผ ํด์ ๋นํจ์จ์ ์ด์์ผ๋ฉฐ ๋ฐ์ดํฐ ํ๋ฆ ์ modal ๋๋๋ง ๊ด๋ จ state ๊ฐ์ ๊ธฐ๋ฅ ์ปดํฌ๋ํธ๋ค์ state๊ฐ์ ์ ์ฅํด๋๋ ๊ฑด ๋ค์ ๋ง์ง ์์ผ๋ฉฐ ์ฝ๋๋ฅผ ๋ณต์กํ๊ฒ ๋ง๋๋ ๋ฌธ์ ๋ฅผ ์ผ๊ธฐ์์ผฐ๋ค.
์ด๋ฅผ ์ํด custom Hooks๋ฅผ ์ฌ์ฉํ์ฌ react-redux ๋์ ์ Modal Visible๋ฅผ ์ปจํธ๋กคํ ์ ์๋๋ก ์ฝ๋๋ฅผ ๋ฆฌํฉํ ๋งํ์๋ค.
Refactor
์ฐ์ Modal Template ์ญํ ์ ํด์ค Modal Component๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ์๋ค.
import React, { Fragment } from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
const Modal = ({ isShowing, hide, children }) => {
if (isShowing) {
document.body.style.setProperty("overflow", "hidden");
return ReactDOM.createPortal(
<Fragment>
<BodyBlackoutStyle onClick={hide} />
{children}
</Fragment>,
document.body
);
} else return null;
};
const BodyBlackoutStyle = styled.div`
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.65);
cursor: pointer;
z-index: 1100;
`;
export default Modal;
์ฐ์ React.createPortal๋ฅผ ์ฌ์ฉํ์ฌ Modal Component๊ฐ ํน์ ํ ๋ถ๋ชจ ์ปดํฌ๋ํธ์ ๊ท์๋์ง ์๊ณ ๋ ๋ฆฝ์ ์ผ๋ก ๋๋๋ง๋ ์ ์๋๋ก ์ฝ๋๋ฅผ ์์ฑํ์๋ค.
https://ko.reactjs.org/docs/portals.html
React.createPortal๋ฅผ ์ฌ์ฉํ ์์ ๋ ธ๋๋ค์ overflow ์์๋ z-index์ ๋ํ ์ฐ์ ์์ ๋ฑ ๋ถ๋ชจ ๋ ธ๋์ ์คํ์ผ์ ๋ํด์ ๋ฐฉํด๋ฅผ ๋ฐ์ง ์๊ณ ์ํ๋ ์ค์ ์ ๋ง์ถฐ ๋ถ๋ชจ ์ปดํฌ๋ํธ์ ์ธ๋ถ์์ ์์ ํ๊ฒ ๋๋๋งํ ์ ์๋ค.
๋ํ Modal ์ฐฝ์ด ํ๋ฉด์ ๋์์ ธ ์๋ ๊ฒฝ์ฐ์๋ ์คํฌ๋กค์ด ๋์ํ๋ฉด ์๋๊ธฐ ๋๋ฌธ์ isShowing ๊ฐ์ด true์ผ ๊ฒฝ์ฐ์ body ํ๊ทธ scroll style ๊ฐ์ hidden์ผ๋ก ์ค์ ํด์ฃผ์๋ค.
๋ค์์ผ๋ก Modal ์ฐฝ๊ณผ ๊ด๋ จ๋ state๊ฐ๊ณผ ํด๋น state๊ฐ๋ค์ ์ด์ฉํ์ฌ Modal ์ฐฝ ๊ด๋ จ ์ค์ ์ ๋ด๋นํด ์ค useModal ์ด๋ผ๋ custom Hooks์ฝ๋๋ฅผ ์์ฑํ์๋ค.
import { useState } from "react";
const useModal = () => {
const [isShowing, setIsShowing] = useState(false);
const [modalInfo, setModalInfo] = useState([]);
const setModalVisible = (...modalInfo) => {
setModalInfo(modalInfo);
setIsShowing(!isShowing);
};
return { isShowing, modalInfo, setModalVisible };
};
export default useModal;
setModalVisible ํจ์๋ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํค๋ Trigger ์์(ex. ํ์๊ฐ์ ๋ฒํผ)์ ์ฝ๋ฐฑ ํจ์๋ก ์ง์ ํด ๋๊ณ ํด๋น ํจ์๊ฐ ํธ์ถ๋๋ฉด ํ์ฌ ์ปดํฌ๋ํธ์ ์ปค์คํฐ๋ง์ด์ง๋ Modal ์ฐฝ์ ๋ณด์ฌ์ฃผ๊ธฐ ์ํ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ modalInfo๋ฅผ ์ธ์๋ก ๋ฐ์ state๊ฐ์ ์ค์ ํ๋ค.
์ด๋ modalInfo์ ๊ฐ์ ๊ฒฝ์ฐ๋ ์ปดํฌ๋ํธ๋ง๋ค ๋ฐ์์ผ ๋๋ ์ ๋ณด๊ฐ ์์ดํ๊ธฐ ๋๋ฌธ์ Rest Parameter ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ ์จ ์ธ์๊ฐ๋ค์ ๋ฐฐ์ด์ ์ ์ฅํ๋๋ก ์ฝ๋๋ฅผ ์์ฑํ์๋ค.
๋ง์ง๋ง์ผ๋ก Modal ์ฐฝ ๊ธฐ๋ฅ์ด ํ์ํ Presentation Component ๋ด์์ Modal Template ๊ธฐ๋ฅ์ ํ๋ Modal ์ปดํฌ๋ํธ์ Modal ๊ด๋ จ ์ปจํธ๋กค๋ฅผ ๋ด๋นํ๋ useModal ์ปดํฌ๋ํธ๋ฅผ importํ์ฌ ํ์ํ ๋์์ ๋ง๊ฒ ์ฝ๋๋ฅผ ์์ฑํ์๋ค.
( ... )
import Modal from "../../components/common/Modal";
import useModal from "../../hooks/useModal";
import ModalTemplate from "../../components/common/ModalTemplate";
import MakeRoomInfoModal from "../../components/common/MakeRoomInfoModal";
const MainForm = ({
( ... )
}) => {
const { isShowing, modalInfo, setModalVisible } = useModal();
( ... )
return (
<Container>
<Modal isShowing={isShowing} hide={setModalVisible}>
<ModalTemplate>
<MakeRoomInfoModal modalInfo={modalInfo} hide={setModalVisible} />
</ModalTemplate>
</Modal>
( ... )
</Container>
);
};
( ... )
export default MainForm;
๋์ ์์
Reference
https://upmostly.com/tutorials/modal-components-react-custom-hooks