useRef
렌더링에 필요하지 않은 값을 참조할 수 있는 React Hook
useRef – React
The library for web and native user interfaces
ko.react.dev
useRef는 처음에 제공한 초기값으로 설정된 단일 current 프로퍼티가 있는 ref 객체를 반환한다.
ref를 사용하면 다음을 보장한다.
- (렌더링할 때마다 재설정되는 일반 변수와 달리) 리렌더링 사이에 정보를 저장할 수 있다.
- (리렌더링을 촉발하는 state 변수와 달리) 변경해도 리렌더링을 촉발하지 않는다. 그렇기 때문에 화면에 표시되는 정보는 state를 사용하는 것이 적합하다.
- (정보가 공유되는 외부 변수와 달리) 각각의 컴포넌트에 로컬로 저장된다.
ref를 사용하여 DOM을 조작하는 것은 특히 일반적 + ref에 DOM 노드에 대한 참조 저장
시도
ref 변수를 DatePicker에서 만들어서 DatepickerCalendar(하위)로 넘겨준다.
DatepickerCalendar 외부의 클릭이 감지되면 picker를 닫는다.
⬇
ERR
Warning: DatepickerCalendar: ref is not a prop.
Trying to access it will result in undefined being returned.
If you need to access the same value within the child component,
you should pass it as a different prop.
ref는 일반적인 prop으로 사용할 수 없다. → forwardRef()를 사용해야 한다.
React 컴포넌트를 forwardRef()라는 함수로 감싸주면, 해당 컴포넌트는 함수는 두 번째 매개 변수를 갖게 되는데, 이를 통해 외부에서 ref prop을 넘길 수 있다.
const DatepickerCalendar = forwardRef<HTMLDivElement, Props>(
(
{startDate, ..., selectEndDate},
ref
) => {
...
});
BUT
넘길 필요가 없었다.
그 이유는 ref를 넘겨서 DatepickerCalendar 컴포넌트만 가지게 하면 아래 하이라이팅한 DateInputBox 영역 또한 외부의 영역으로 감지된다.
그렇게 되면 이벤트가 의도와는 다르게 발생하기 때문에 저 부분을 제외해줘야 한다.
그렇다면 결론은 Ref를 DateInputBox와 DatepickerCalendar를 모두 포함하는 영역인 DatePickerWrapper에 바인딩해주자라고 나온다.
즉, 빨간 선 안은 전부 Inner / 그 외는 외부영역
재구현
1. ref 선언
const pickerRef = useRef<HTMLDivElement>(null);
2. ref를 DatePicker 컴포넌트 최상단 div에 바인딩
3. 외부 클릭 시 실행할 함수
const handleClickOutside = (e: MouseEvent) => {
if (pickerRef.current && !pickerRef.current.contains(e.target as Node)) {
handlePopup();
}
};
!pickerRef.current.contains(e.target as Node)) 이 부분이 외부 클릭 조건
4. 클릭 이벤트 연결
useEffect(() => {
document.addEventListener("click", handleClickOutside);
return () => {
document.removeEventListener("click", handleClickOutside);
};
}, [pickerRef.current]);
의존성 배열에 pickerRef.current를 넣어주지 않으면 컴포넌트가 최초로 렌더링될 때 한번 클릭 이벤트가 연결된다.
→ 이때 handleClickOutside, handlePopup 함수는 잘 연결이 되지만 이상하게도 isOpen이 계속해서 false 값을 가지고 있다. (setIsOpen 문이 있음에도)
pickerRef.current를 넣어주면 최초 렌더링 시 + picker가 처음 열렸을 때 다시 클릭 이벤트가 연결된다.
→ 원하는 대로 잘 동작한다.
솔직히 이유를 발견하지는 못했다.
New Issue
DatePicker와 DateRangePicker를 같이 두면 처음 한 번은 같이 켜진다… (그 다음부터는 따로따로 켜지긴 함)
물론 해당 컴포넌트들을 같이 쓸 일이 없을 수도 있다. 하지만 만약 그런 일이 있다면..?
얼른 해결하자.
원인
외부 영역 클릭 시 실행되는 함수인 handleClickOutside가 실행돼서 그런 것
해결
handleClickOutside 함수 조건문에 isOpen 추가
const handleClickOutside = (e: MouseEvent) => {
if (
pickerRef.current &&
!pickerRef.current.contains(e.target as Node) &&
isOpen
) {
handlePopup();
}
};
[ 참고 자료 ]
'React' 카테고리의 다른 글
[ React ] TimeTable 시간표 컴포넌트 구현 (0) | 2024.06.24 |
---|---|
[ React ] weekSelector 컴포넌트 구현 (0) | 2024.06.21 |
[ React ] DatePicker 컴포넌트 직접 만들기 (0) | 2024.06.17 |
[ React ] react-router-dom의 outlet으로 layout 구성하기 (0) | 2024.06.13 |
[ React ] 캘린더 컴포넌트 직접 만들기 (feat. date-fns) (0) | 2024.06.12 |