import { useEffect } from "react";
import React from "react";
import classes from "./Modal.module.css";
function Modal({ id, children, closeModal }) {
//모달창 실행시, 백그라운드 스크롤은 중지
useEffect(() => {
const $body = document.querySelector("body");
const overflow = $body.style.overflow;
$body.style.overflow = "hidden";
return () => {
$body.style.overflow = overflow;
};
}, []);
let selectedChildren = React.Children.map(children, (child) => {
if(child.props.id === id){
return child;
}else{
return null;
}
})
return (
<>
<div className={classes.backDrop} onClick={closeModal} />
<dialog open className={classes.modal}>
{selectedChildren}
</dialog>
</>
);
}
export default Modal;
책 검색 모달창을 위해서 사용하던 컴포넌트인데, 로그인창도 모달로 표출되게 하기 위해 이 컴포넌트를 재사용 하고싶었다. 몇가지 난관들이 있었는데,
1. Modal의 열고 닫음 상태 관리를 전역으로 올려야 했다
useContext 를 처음으로 사용! (성장)
: 상태를 사용해야하는 하위 컴포넌트를 공통으로 가지는 최상위 컴포넌트에서 createContext()를 해줘야 했는데,
header의 메뉴에서 사용해야 했기 때문에 RootRayout에서 만들어줬다.
import { createContext } from "react";
import Header from "../components/Header";
import { Outlet } from "react-router-dom";
import { useState } from 'react';
const modalContext = createContext();
function RootLayout() {
const [isOpen, setIsOpen] = useState(false);
const openModal = () => {
setIsOpen(true);
};
const closeModal = () => {
setIsOpen(false);
};
return (
<>
<modalContext.Provider value={{ isOpen, openModal, closeModal }}>
<Header />
<Outlet context={{ isOpen, openModal, closeModal }}/>
</modalContext.Provider>
</>
);
}
export default RootLayout;
export {modalContext};
2. <context Provider> 로 jsx 태그들을 잘 감싸주어 하위 컴포넌트에서 공유할수 있게 해주면
<Header> 컴포넌트에서 value들을 사용할 수 있다.
그런데 또 다른 난관에 봉착..
<Outlet>은 컴포넌트가 아니라, react-router-dom 에서 제공하는 훅으로써, 여러개의 하위 컴포넌트를 묶어두고 있다.
따라서 Outlet에서는 useContext가 안먹힌다.. ㅠㅠ
다행히, 리액트 라우터 v6.1 이상부터는 useOutletContext를 사용할 수 있다. Outlet의 속성에 context를 추가해주고 사용할 value를 넣어주자.
import { useContext, useState } from "react";
import classes from "./Login.module.css";
import { Link, useOutletContext, useSearchParams } from "react-router-dom";
import { auth } from "../firebase";
import SignUp from "./SignUp";
import Modal from "../components/Modal";
function Login() {
const [id, setId] = useState();
const [password, setPassword] = useState();
const { isOpen, openModal, closeModal } = useOutletContext();
return (
<div className={classes.container}>
<section className={classes.loginDiv}>
<>
<p className={classes.logo}>(제작중)Dive into your Ocean</p>
<form>
<div className={classes.input}>
<input
type="text"
onChange={(e) => setId(e.target.value)}
></input>
<input type="password"></input>
<button>로그인</button>
</div>
<p onClick={openModal} className={classes.signUp}>계정이 없으신가요?</p>
</form>
</>
{isOpen &&
<Modal closeModal={closeModal} id={1} >
<SignUp id={1} />
</Modal>
}
</section>
</div>
);
}
export default Login;
그다음, 사용할 하위 컴포넌트 Login으로 돌아와 useOutletContext를 import해준 다음
변수에 useOutletContext()로 할당을 해주면 이제 사용 가능!
진짜 다 끝난줄 알았다..
또 다른 난관 ^^
3. 모달 컴포넌트를 공유 하긴 했는데, children 도 공유해버리네?
내 상상속에서는, modal의 양식만 가져와서 쓰면 각각의 모달로 사용 할 수 있을것 같았는데
코드를 이렇게 짜니까 한가지 상태의 Modal로 똑같이 렌더링이 되어버렸다.
header에서 쓰는 모달 ( 책검색창)과 login의 모달(회원가입 창)이 같이 렌더링이 되는..
이 현상을 해결하기 위해서 열심히 찾아보니
이 함수를 이용해서 필터링을 할 수 있는듯 하여
각각의 모달 컴포넌트에 id를 주고, 자식요소에도 같은 id 값을 부여한뒤
id값이 같은 child만 렌더링 하려 했으나 생각대로 안된다. 여전히 한꺼번에 렌더링이 되는데..
해결할 수 있을 것인가.
두둥탁
+
생각해보니
modal 컴포넌트의 isOpen, 열림닫힘 메소드를 전역상태로 관리하는 것 자체가 결국 같은 상태객체를 전역적으로 공유한다는 것이니까, 랜더링을 아무리 다르게 하려고 해도. 굳이? 라는 생각이 들었다. 전체 컴포넌트에서 모달은 현재 2군데 밖에 사용하지 않는데.. 코드중복이긴 해도 각 컴포넌트에서 상태를 따로 관리하는게 그렇게까지 비효율적인걸까?
일단 상태를 각 컴포넌트에서 관리해주는 방법으로 바꿨다.
'리액트' 카테고리의 다른 글
useEffect 함수와 return (0) | 2023.06.23 |
---|---|
[배포] Glitch 로 fake server 호스팅 하고 Netlify로 배포해보자 (0) | 2023.06.19 |
React :: fetch()로 서버에서 데이터 삭제하기 (0) | 2023.06.03 |
React 에서 fetch로 네이버 검색 API 사용하기 (feat.Vite 환경/프록시) (0) | 2023.05.28 |
Fetch API (0) | 2023.05.24 |