'react-datepicker'에서 날짜 상태를 관리할 때 겪었던 시행착오

2024. 8. 3. 17:19·Frontend

1. react-datepicker로 달력을 구현하기로 했다.

현재 진행하고 있는 프로젝트에서 달력으로 날짜를 선택하는 것을 구현해야 했다. 달력을 직접 구현하는 것은 힘들 것 같아서 ‘react-datepicker’ 라이브러리를 사용했다. 일단 달력 UI를 피그마에 맞게 커스텀하는 것에 집중했다. 달력을 커스텀하는 것은 생각보다 어려웠지만, 여러 번 시행착오를 겪으니 비슷하게 보이게 만드는데에는 성공할 수 있었다.

달력 피그마 캡처

 

실제로 구현한 달력 캡처

 


2. 이제 날짜 상태를 관리하자

이제는 선택한 날짜를 상태로 관리해야 한다.

먼저 구조를 설명하자면, SelectBox를 클릭하면 내가 만들어놓은 Dialog 컴포넌트가 나오고, Dialog 컴포넌트의 children에 Datepicker가 있고 그 밑에는 확인(confirm) 버튼과 닫기(cancel) 버튼이 있다.

 

우선 나는 selectedDate(선택된 날짜) 상태와 tempDate(임시 날짜 → Datepicker에서 선택 중인 날짜) 상태를 생성해서 ‘확인’ 버튼을 누르면 tempDate를 selectedDate로 선택하게 했다.

const SelectBox = ({ onChange }: SelectBoxProps) => {
  const [selectedDate, setSelectedDate] = useState<Date | null>(null);
  const [tempDate, setTempDate] = useState<Date | null>(null);
  const [isOpen, setIsOpen] = useState(false);
  const { openModal, closeModal } = useModal();
  const modalState = useContext(ModalStateContext);

  const handleDateChange = (date: Date | null) => {
    console.log("Date changed in DatePicker:", date);
    setTempDate(date);
  };

  const handleConfirm = useCallback(() => {
    console.log("Confirming date:", tempDate);
    if (tempDate) {
      setSelectedDate(tempDate);
      if (onChange) {
        onChange(tempDate);
      }
    }
    setIsOpen(false);
    closeModal();
  }, [tempDate, onChange, closeModal]);

  const handleCancel = useCallback(() => {
    setTempDate(null);
    setIsOpen(false);
    closeModal();
  }, [closeModal]); 
  
  (...)

 

하지만 문제가 생겼다. 날짜를 고르고 ‘확인’ 버튼을 눌러도, selectedDate는 null로 나온다는 것이었다. 즉, 바로 반영이 안되었다. console.log로 확인해봤을 때 tempDate는 날짜가 잘 출력이 되어서 tempDate가 selectedDate로 전달이 잘 안되는 구나라고 유추할 수 있었다.


3. async/await를 써볼까

async/await를 도입하면 문제를 해결할 수 있을까. 한 번 도입해봤다.

const handleConfirm = useCallback(async () => {
    console.log("Confirming date:", tempDate);
    
    // 상태 업데이트를 기다립니다
    await new Promise<void>(resolve => {
      setSelectedDate(tempDate);
      resolve();
    });

    if (onChange) {
      onChange(tempDate);
    }
    setIsOpen(false);
    closeModal();
  }, [tempDate, onChange, closeModal]);

setSelectedDate(tempDate)가 먼저 실행되고 나서, 부모 컴포넌트의 onChange 콜백을 호출했다.

이제 잘되지 않을까? 하고 희망을 가졌었다. 바뀌기는 바뀌었는데.. 조금 이상하게 바뀌었다.

 

로그를 찍어보니

Date changed in DatePicker: Sun Aug 11 2024 00:00:00 GMT+0900 (한국 표준시) Confirming date: null

Date changed in DatePicker: Thu Aug 22 2024 00:00:00 GMT+0900 (한국 표준시) Confirming date: Sun Aug 11 2024 00:00:00 GMT+0900 (한국 표준시)

Date changed in DatePicker: Wed Aug 07 2024 00:00:00 GMT+0900 (한국 표준시) Confirming date: Thu Aug 22 2024 00:00:00 GMT+0900 (한국 표준시)

Date changed in DatePicker: Sat Aug 03 2024 00:00:00 GMT+0900 (한국 표준시) Confirming date: Wed Aug 07 2024 00:00:00 GMT+0900 (한국 표준시)

 

보면 알겠지만 날짜가 하루씩 밀리게 되었다!! 왜 이런 일이 일어나게 되었을까? AI는 상태 업데이트의 비동기적 특성과 클로저 문제라고 간단하게 언급해주었다. 아무튼, async/await 말고 다른 방법을 사용해야 한다.


4. useRef를 써보자

AI는 ‘useRef’를 사용해보라고 추천했다. 그러면 코드를 바꿔보자

일단 tempDate 상태는 삭제해주었다. 그대신 tempDateRef를 도입했다.

// const [tempDate, setTempDate] = useState<Date | null>(null);
  const tempDateRef = useRef<Date | null>(null);

 

그리고 각 함수에 tempDateRef를 적용했다.

const handleDateChange = (date: Date | null) => {
    console.log('Date changed in DatePicker:', date);
    tempDateRef.current = date;
  };

  const handleConfirm = useCallback(() => {
    console.log('Confirming date:', tempDateRef.current);
    setSelectedDate(tempDateRef.current);
    if (onChange) {
      onChange(tempDateRef.current);
    }
    setIsOpen(false);
    closeModal();
  }, [onChange, closeModal]);

  const handleCancel = useCallback(() => {
    tempDateRef.current = null;
    setIsOpen(false);
    closeModal();
  }, [closeModal]);

useState 대신 useRef를 적용하니 날짜 적용이 잘 되었다!

 

물론 의도하지 않은점이 하나 있었는데, handleCancel의 ‘tempDateRef.current = null’로 설정했던 것은, ‘닫기’ 버튼을 눌렀을 때 그냥 날짜가 초기화되는 것을 기대했던 거였다. 하지만 그냥 SelectBox에 이전에 선택한 날짜가 표시되었다. 사실 이것도 괜찮긴한데, 왜 날짜가 그대로 표시되는 건지 궁금해서 AI에게 물어봤다.

 

5. AI의 답변

AI는 이렇게 답변했다.

 

좋은 관찰이십니다! 이 현상의 이유를 설명해 드리겠습니다:

  1. 클로저(Closure)와 비동기 실행: handleCancel 함수는 모달이 닫힐 때 비동기적으로 실행됩니다. 이 시점에서 tempDateRef.current를 null로 설정하지만, 이미 handleConfirm 함수가 이전에 실행되어 날짜를 설정했을 수 있습니다.
  2. 상태 업데이트의 순서: React의 상태 업데이트는 비동기적으로 일어나며, 배치(batch) 처리됩니다. handleCancel에서 tempDateRef.current를 null로 설정하더라도, 이는 다음 렌더링 사이클에서 반영됩니다.
  3. selectedDate 상태의 독립성: tempDateRef는 임시 선택을 위해 사용되고, selectedDate는 최종 선택된 날짜를 저장합니다. handleCancel에서 tempDateRef를 초기화해도 selectedDate는 변경되지 않습니다.
  4. 모달 닫힘 로직: 모달이 닫힐 때 tempDateRef를 초기화하지만, 이는 다음번 모달을 열 때 사용될 초기값에만 영향을 미칩니다. 이미 선택되고 확인된 날짜(selectedDate)는 그대로 유지됩니다.
  5. useRef의 특성: useRef의 값을 변경해도 컴포넌트의 리렌더링을 트리거하지 않습니다. 따라서 tempDateRef.current를 변경해도 즉시 UI에 반영되지 않습니다.

이 동작이 의도한 대로라면 좋습니다! 사용자가 날짜를 선택하고 취소해도 이전에 선택한 날짜가 유지되는 것이 UX 관점에서 더 좋을 수 있습니다. 만약 취소 시 날짜를 완전히 초기화하고 싶다면, handleCancel 함수에서 setSelectedDate(null)을 추가로 호출하면 됩니다.

이 방식은 사용자가 실수로 모달을 닫아도 이전 선택을 유지할 수 있어 편리합니다. 필요에 따라 동작을 조정할 수 있으니, 현재 동작이 요구사항에 맞다면 그대로 유지하셔도 좋습니다.


5번 설명이 가장 큰 이유인것 같았다.

그러면 그냥 ‘tempDateRef.current = null;’ 이 문구는 삭제해도 괜찮을 것 같아서 삭제했다.

 

결론: react-datepicker에서 날짜를 관리할 때는 ‘useRef’를 사용하자!

 

저작자표시 비영리 변경금지 (새창열림)

'Frontend' 카테고리의 다른 글

Vite 프로젝트에 PWA와 FCM 푸시 알림 적용하기  (1) 2025.01.17
[코딩테스트] Javascript로 코딩테스트 보기 전 봐야 할 핵심로직 정리  (0) 2025.01.11
프로젝트 팀원 코드를 보고 컴포넌트 만드는 법 배우기(typescript + clsx)  (1) 2024.07.21
[Next.js] Next.js(SSR 환경)에서 css 애니메이션이 작동 안하는 이유  (1) 2024.07.12
[HTML] HTML 구조, <script> 태그, HTML 박스모델 등에 대한 요약  (0) 2024.03.14
'Frontend' 카테고리의 다른 글
  • Vite 프로젝트에 PWA와 FCM 푸시 알림 적용하기
  • [코딩테스트] Javascript로 코딩테스트 보기 전 봐야 할 핵심로직 정리
  • 프로젝트 팀원 코드를 보고 컴포넌트 만드는 법 배우기(typescript + clsx)
  • [Next.js] Next.js(SSR 환경)에서 css 애니메이션이 작동 안하는 이유
퀵차분
퀵차분
Web Developer 🥐
  • 퀵차분
    QC's Devlog
    퀵차분
  • 전체
    오늘
    어제
    • 분류 전체보기 (178)
      • Frontend (31)
      • Fedify (4)
      • Study (42)
        • NestJS (2)
        • Node.js (3)
        • Modern JS Deep Dive (13)
        • SQL (1)
        • Network (1)
        • 프롬프트 엔지니어링 (4)
        • 인공지능 (9)
        • 시스템프로그래밍 (11)
        • 선형대수학 (1)
      • Intern (4)
      • KUIT (21)
      • Algorithm (48)
        • Baekjoon(C++) (26)
        • Programmers(JavaScript) (22)
      • 우아한테크코스(프리코스) (4)
      • Project (10)
        • crohasang_page (3)
        • PROlog (4)
        • Nomadcoder (2)
      • 생각 (4)
      • Event (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    자바스크립트
    HTML
    리액트
    javascript
    typescript
    인공지능
    음악추천
    티스토리챌린지
    react
    백준
    알고리즘
    프로그래머스 자바스크립트
    타입스크립트
    next.js
    프로그래머스
    프론트엔드
    시스템프로그래밍
    fedify
    KUIT
    오블완
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
퀵차분
'react-datepicker'에서 날짜 상태를 관리할 때 겪었던 시행착오
상단으로

티스토리툴바