TaskMaster - DatePicker 적용!

nooori·2024년 6월 21일
post-thumbnail

DatePicker를 사용하게 된 이유

TaskMaster 진행하면서 구현하고싶었던 기능 중 하나가 '진행 중'인 카테고리에 마감 기한을 설정할 수 있도록 하는 것이었다. 사용자가 봤을 때 진행하고 있는 작업의 마감 기한이 표시되어있으면 작업 관리가 좀 더 효율적이고 편리할 것이라고 생각했기 때문이다. 그리고 직접 코드를 작성하는 것보다는 'DatePicker'를 사용해보기로 했다. DatePicker는 날짜 선택 기능을 쉽게 구현할 수 있도록 도와주는 라이브러리로, 다양한 설정 옵션과 사용자 정의 기능을 제공한다. 단순히 적용하는 것 뿐인데도 쉽지 않았다😰

사용해보기 전에 뭔가 코드가 복잡해질 것 같아서 Calendar라는 컴포넌트를 따로 만들고 여기서 Datepicker를 사용한 캘린더를 구현해보기로 했다. 그리고 사용 방법과 디자인 css는 아래 링크를 참고했다.
https://doooodle932.tistory.com/150

위 블로그를 참고하니 calendar 컴포넌트를 클릭하면 년,월,일을 선택할 수 있는 창이 뜨도록 구현하는 것은 어렵지 않았다. 설정할 수 있는 옵션은 여러가지가 있는데

  • dateFormat : 날짜 형식을 지정한다.'yyyy.MM.dd'을 사용했고 연도, 월, 일 순서로 표시된다.
  • formatWeekDay={(nameOfDay) => nameOfDay.substring(0, 1)} : 요일 이름을 줄여서 표시한다. 예를 들어 "월요일"을 "월"로 표시한다.
  • showYearDropdown : 연도 선택할 수 있는 드롭 다운을 보여준다
  • shouldCloseOnSelect : 날짜를 선택한 후에 DatePicker가 자동으로 닫힌다.
  • minDate={new Date('2020-01-01')} : 선택할 수 있는 최소 날짜를 설정한다.
  • selected={selectedDate} : 선택된 날짜를 설정한다.

이 외에도 dayClassName, onChange, renderCustomHeader 등의 옵션을 설정하여 프로젝트의 요구에 맞게 날짜 선택 기능을 구현할 수 있었다.

날짜를 고정할 수는 없나?

문제

내가 구현하고자 하는 것은 마감 기한이 선택이 되면 새로 고침을 하거나 페이지를 나갔다가 다시 돌아와도 날짜가 유지되는 것이었다. 그렇지 않으면 항상 오늘 날짜로 날짜가 변하기 때문에 마감 기한의 기능이 쓸모가 없다😟 그래서 DatePicker의 옵션중에 selected라는 선택된 날짜를 전달하는 옵션을 잘 활용해보면 구현할 수 있지 않을까하는 생각이 들었다.

해결 과정

  1. 우선 날짜를 기억하려면 localStorage에 날짜를 저장해야하기 때문에 해당 날짜를 localStorage에 저장하는 handleCheck 함수를 만들었다.
const handleCheck = (date) => {
    if (date) {
      localStorage.setItem('tasks', date.toISOString());
      setFixedDate(date);
    }
    setSelectedDate(date);
  }
  1. 각 DatePicker는 고정된 날짜를 표시하기 위해 fixedDate라는 상태를 만들고 이 상태는 useEffect hook을 통해 localstorage에 초기화되고 업데이트될 수 있도록 한다.
const [fixedDate, setFixedDate] = useState(null);

useEffect(() => {
  const storedDate = localStorage.getItem('tasks');
  if (storedDate) {
    setFixedDate(new Date(storedDate))
  }
},['tasks'])
selected={fixedDate || selectedDate}

그리고 selected 옵션을 위와 같이 설정해서 fixedDate가 있으면 그 값을 사용하고, 없으면 selectedDate를 사용하도록 했다.

  1. 이렇게 하니까 마감 기한을 설정하고 데이터 유지는 되는데 작업 목록에 있는 DatePicker가 모두 변경이 되었다. 각 작업 목록마다 각기 다른 마감 기한을 가져야하는데ㅠ 그래서 생각한게 localStorage에 저장할 때 하나의 'tasks'에 저장하는 것이 아니라 각자 다른 항목에 저장하는 것이다! 그래서 일단 Calendar에 tasks의 id도 함께 전달받아서 고유 키를 만들었다.
const localStoragekey = `fixedSelectedDate_${id}`;

그리고 'tasks' 대신 localStoragekey를 사용해서 고유 key가 적용되도록 하였다.

const [fixedDate, setFixedDate] = useState(null);

useEffect(() => {
  const storedDate = localStorage.getItem(localStoragekey);
  if (storedDate) {
    setFixedDate(new Date(storedDate))
  }
},[localStoragekey])

const handleCheck = (date) => {
  if (date) {
    localStorage.setItem(localStoragekey, date.toISOString());
    setFixedDate(date);
  }
  setSelectedDate(date);
}

컴포넌트가 마운트될 때, useEffect hook을 사용하여 고유 key를 통해 해당 DatePicker의 localStorageKey에 저장된 날짜를 가져와 fixedDate 상태를 업데이트 한다. 이를 통해 사용자가 페이지를 새로고침하거나 다시 방문해도 고정된 날짜가 유지되도록 하였다.

결과

각 DatePicker는 고유한 localStorage 키를 사용하여 자체적으로 날짜를 관리하고 저장할 수 있었다. 따라서 DatePicker는 독립적으로 동작하며, 서로에게 영향을 미치지 않게 동작한다.

전체 코드

import React, { useEffect, useState } from 'react';
import DatePicker from 'react-datepicker';
import { getMonth, getYear } from 'date-fns';
import 'react-datepicker/dist/react-datepicker.css';
import { MdOutlineNavigateBefore, MdNavigateNext } from "react-icons/md";

const YEARS = Array.from({ length: getYear(new Date()) + 1 - 2000 }, (_, i) => getYear(new Date()) - i);
const MONTHS = [
  'January', 'February', 'March', 'April', 'May', 'June',
  'July', 'August', 'September', 'October', 'November', 'December',
];

export const Calendar = ({ id, selectedDate, setSelectedDate }) => {
  const localStoragekey = `fixedSelectedDate_${id}`;

  const [fixedDate, setFixedDate] = useState(null);

  useEffect(() => {
    const storedDate = localStorage.getItem(localStoragekey);
    if (storedDate) {
      setFixedDate(new Date(storedDate))
    }
  },[localStoragekey])

  const handleCheck = (date) => {
    if (date) {
      localStorage.setItem(localStoragekey, date.toISOString());
      setFixedDate(date);
    }
    setSelectedDate(date);
  }

  return (
    <div className='w-full max-w-xs mx-auto'>
      <DatePicker
        dateFormat='yyyy.MM.dd'
        formatWeekDay={(nameOfDay) => nameOfDay.substring(0, 1)}
        showYearDropdown
        scrollableYearDropdown
        shouldCloseOnSelect={true}
        yearDropdownItemNumber={100}
        minDate={new Date('2020-01-01')}
        selected={fixedDate || selectedDate}
        dayClassName={(d) => (d.getDate() === (selectedDate ? selectedDate.getDate() : 0) ? 'bg-orange-500 rounded-full text-white' : 'hover:bg-gray-200')}
        onChange={(date) => handleCheck(date)}
        className="flex items-center w-28 h-8 border border-gray-300 rounded-md px-4 dark:text-black"
        renderCustomHeader={({ date, changeYear, decreaseMonth, increaseMonth, prevMonthButtonDisabled, nextMonthButtonDisabled }) => (
          <div>
            <div>
              <span>{MONTHS[getMonth(date)]}</span>
              <select
                value={getYear(date)}
                onChange={({ target: { value } }) => changeYear(+value)}
              >
                {YEARS.map((option) => (
                  <option key={option} value={option} className='bg-black text-white'>
                    {option}
                  </option>
                ))}
              </select>
            </div>
            <div>
              <button
                type='button'
                onClick={decreaseMonth}
                disabled={prevMonthButtonDisabled}
              >
                <MdOutlineNavigateBefore />
              </button>
              <button
                type='button'
                onClick={increaseMonth}
                disabled={nextMonthButtonDisabled}
              >
                <MdNavigateNext />
              </button>
            </div>
          </div>
        )}
      />
    </div>
  );
};

느낀 점

React DatePicker 라이브러리를 사용하여 날짜 선택 기능을 구현해 본 결과, 보기 쉬운 날짜 선택 UI를 내 방식대로 커스터마이즈 할 수 있는 좋은 경험이었다. 사용자 인터페이스가 직관적이어서 사용자들이 쉽게 날짜를 선택할 수 있고 다양한 형식의 날짜와 시간을 지원하기 때문에 다양한 요구사항을 충족시킬 수 있다고 생각했다. 특히, 다른 라이브러리들과의 통합이 용이하다는 점이 매우 마음에 들었다. Recoil을 사용해서 선택된 날짜를 전역 상태로 관리하고 'localStorage'와 동기화하는 과정이 자연스러웠고 충돌이 없었다. 날짜 선택 기능이 필요한 프로젝트에 매우 유용한 도구라고 생각이 들었고 다음에도 캘린더 기능을 구현할 때 DatePicker 라이브러리를 또 사용할 계획이다.

0개의 댓글