이전에는 드롭다운을 onMouseEnter와 onMouseLeave를 이용하여 구현 하였는데 이것 말고도 다른 방법이 있을 것 같아 찾아보다 hooks를 만들어 구현하는 방법을 찾게 되었다.
먼저 header에 해당 버튼을 눌렀을 경우 드롭다운이 발생하도록 하기 위해서는 눌렀을 때의 상태를 저장해둘 state가 필요하다. isOpen이라는 상태를 true와 fasle값으로 변하도록 하였다.
위의 조건을 보면 isOpen의 상태는 드롭다운 메뉴와 클릭이벤트로 인해 바뀌는 값들인 것이다. 그리고 드롭다운안의 메뉴끼리의 isOpen은 독립적으로 움직여야 한다.
문제는 3번인데, 이 문제를 해결하기 위해서는 useEffect를 활용해야한다.
const ref = useRef(null)
const [isOpen, setIsOpen] = useState(false)
useEffect(() => {
const onClick = (e) => {
// 드롭다운 메뉴 이외의 공간의 클릭
if(ref.current !== null && !ref.current.contains(e.target)){
setIsOpen(false)
}else if(isOpen){
window.addEventListener('click', onClick)
}
}
return () => {
window.removeEventListener('click', onClick)
}
},[isOpen])
여기서 구현해야할 것은 드롭다운 공간의 이외의 클릭을 감지하는 것이다. 드롭 다운 메뉴를 클릭했을 때 !
와 같은 것이다.
클릭이벤트의 event.target이 클릭한 요소를 가르킨다면 어떤 요소를 클릭했는지 알 수 있고, 목표로하는 요소를 ref가 참조할 수 있도록 할 수 있으므로 해당 코드를 사용하면되겠다.
useDetectClose
import { useEffect, useState, useRef } from 'react'
const useDetectClose = (initialState) => {
const [isOpen, setIsOpen] = useState(initialState)
const ref = useRef(null)
const removeHandler = () => {
setIsOpen(!isOpen)
}
useEffect(() => {
const onClick = (e) => {
if (ref.current !== null && !ref.current.contains(e.target)) {
setIsOpen(!isOpen)
}
}
if (isOpen) {
window.addEventListener('click', onClick)
}
return () => {
window.removeEventListener('click', onClick)
}
}, [isOpen])
return [isOpen, ref, removeHandler]
}
export default useDetectClose
isOpen은 true, false값이기 때문에 메뉴박스의 조건부 렌더링에 사용될 수 있고, 초기값은 항상 false가 되겠다.
ref는 드롭다운 메뉴에 걸어주어 해당 DOM을 지정해줄 수 있겠고, removeHandler는 드롭다운 메뉴의 onClick이벤트 콜백 함수로등록해주면 되겠다.
Header.jsx
import React from 'react'
import logo from '../../assets/logo.jpeg'
import { HeaderSection, MenuButton, Wrapper } from './styled'
import useDetectClose from '../../Hooks/useDetectClose'
import DropDwon from '../DropDwon/DropDwon'
const Header = () => {
const [myPageIsOpen, myPageRef, myPageHandler] = useDetectClose(false)
return (
<>
<HeaderSection>
<img src={logo} />
<Wrapper>
<MenuButton onClick={myPageHandler} ref={myPageRef}>
Menu
<DropDwon myPageIsOpen={myPageIsOpen} />
</MenuButton>
</Wrapper>
</HeaderSection>
</>
)
}
export default Header