클릭시 모달창이 나오는 기능을 구현하던중 재사용이 만을듯하여 커스텀훅으로 만들기로 하였다.
생각해야할 이슈는
1. 모달바깥쪽을 클릭했을때 모달이 사라져야한다.
2. 모달이 생겼을때 스크롤을 없애야한다.
import React, { ReactElement } from 'react';
const BottomModal = (): ReactElement => {
return (
<S.Container >
<S.Modal >
<S.Title>{title}</S.Title>
<S.Btn onClick={onLastBtnClick}>{content}</S.Btn2>
</S.Box>
</S.Container>
);
};
export default BottomModal;
컴포넌트로 모달을 만들어 재사용할것이기 때문에 먼저 jsx구조와 스타일을 적용하였다.
import React, { ReactElement, useRef, useState } from 'react';
const BottomModal = (): ReactElement => {
const visibleContentRef = useRef<null || HTMLDivElement>(null)
const [modalOn,setModalOn] = useState(false)
const clickOutSide = (e:React.MouseEvent<HTMLDicElement, MouseEvent>) => {
if (
e.target instanceof HTMLDivElement &&
visibleContentRef.current &&
!visibleContentRef.current.contains(e.target) &&
setModalOn
) {
setModalOn(false);
}
}
return (
{modalOn
? <S.Container onClick={clickOutSide}>
<S.Modal ref={visibleContentRef}>
<S.Title>{title}</S.Title>
<S.Btn>{content}</S.Btn2>
</S.Box>
</S.Container>
: null
}
);
};
export default BottomModal;
// useClickOutside.ts
import { useState, useRef, useEffect } from 'react';
export const useClickOutside = (
initialValue: boolean,
): [
React.MutableRefObject<HTMLDivElement | null>,
boolean,
React.Dispatch<React.SetStateAction<boolean>>,
(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void,
] => {
const visibleContentRef = useRef<null | HTMLDivElement>(null);
const [modalOn, setModalOn] = useState<boolean>(initialValue);
const clickOutside = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (
e.target instanceof HTMLDivElement &&
visibleContentRef.current &&
!visibleContentRef.current.contains(e.target) &&
setModalOn
) {
setModalOn(false);
}
};
return [visibleContentRef, modalOn, setModalOn, clickOutside];
};
커스텀훅으로 만들었으니 이제 사용을 해보자
const ThemeTemplate = (): ReactElement => {
const [visibleContentRef, modalOn, setModalOn, clickOutside] = useClickOutside(false);
return (
<section>
<div onClick={() => setSequenceOn(true)}>
클릭
</div>
{/* 모달창 */}
{
modalOn ?
<BottomModal
visibleContentRef={visibleSequenceRef}
clickOutside={clickOutside}
setModalState={setModalOn}
/> :
null
}
</section>
);
};
export default ThemeTemplate;
이제 useClickOutSide는 커스텀훅으로써 매번 사용할수 있고, modal컴포넌트또한 값을 주입해주는 방법으로 재사용이 가능해졌다.
//useClickOustside.ts
import { useState, useRef, useEffect } from 'react';
export const useClickOutside = (
initialValue: boolean,
): [
React.MutableRefObject<HTMLDivElement | null>,
boolean,
React.Dispatch<React.SetStateAction<boolean>>,
(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void,
] => {
const visibleContentRef = useRef<null | HTMLDivElement>(null);
const [modalOn, setModalOn] = useState<boolean>(initialValue);
useEffect(() => {
if (modalOn) {
document.body.style.overflowY = 'hidden';
} else {
document.body.style.overflowY = 'initial';
}
}, [modalOn]);
const clickOutside = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (
e.target instanceof HTMLDivElement &&
visibleContentRef.current &&
!visibleContentRef.current.contains(e.target) &&
setModalOn
) {
setModalOn(false);
}
};
return [visibleContentRef, modalOn, setModalOn, clickOutside];
};
모달의 변화를 감지하는 useEffect를 커스텀훅에 넣어서 body.style.overflowY로 on/off를 컨트롤 하였다.