← 목록으로

React 달력 라이브러리

React 달력 라이브러리 완벽 가이드: 날짜 선택부터 일정 관리까지

서론

웹 애플리케이션에서 날짜 선택과 달력 기능은 예약 시스템, 일정 관리, 폼 입력 등 다양한 곳에서 필수적입니다. React 생태계에는 여러 달력 라이브러리가 있으며, 각각의 특징과 사용 사례가 다릅니다. 이 글에서는 주요 React 달력 라이브러리들을 비교하고, Next.js와 함께 사용하는 실전 방법을 다루겠습니다.

1. 주요 달력 라이브러리 비교

1.1 라이브러리 선택 기준

  • 번들 크기: 애플리케이션 성능에 직접적인 영향
  • 커스터마이징: 디자인 요구사항에 맞게 수정 가능한지
  • 접근성: 키보드 네비게이션과 스크린 리더 지원
  • 타입스크립트 지원: 타입 안정성
  • 활발한 유지보수: 최신 React 버전과의 호환성

1.2 인기 라이브러리 비교

라이브러리번들 크기특징사용 사례
react-calendar~15KB가볍고 단순한 달력기본 날짜 선택
react-datepicker~50KB기능이 풍부한 날짜 선택기복잡한 날짜 입력
react-day-picker~20KB접근성 우수, 커스터마이징 용이접근성 중요한 앱
date-fns~70KB날짜 유틸리티 라이브러리날짜 계산과 포맷팅

2. react-calendar: 가벼운 달력 컴포넌트

2.1 설치 및 기본 사용

npm install react-calendar
'use client';

import { useState } from 'react';
import Calendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';

export default function SimpleCalendar() {
  const [date, setDate] = useState(new Date());

  return (
    <div className="flex flex-col items-center p-4">
      <Calendar onChange={setDate} value={date} />
      <p className="mt-4">선택한 날짜: {date.toLocaleDateString()}</p>
    </div>
  );
}

2.2 커스터마이징

'use client';

import { useState } from 'react';
import Calendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';

export default function CustomCalendar() {
  const [date, setDate] = useState(new Date());

  return (
    <div className="flex flex-col items-center p-4">
      <Calendar
        onChange={setDate}
        value={date}
        className="rounded-lg border-2 border-gray-300 p-4"
        tileClassName={({ date, view }) => {
          // 오늘 날짜 스타일링
          if (date.toDateString() === new Date().toDateString()) {
            return 'bg-blue-500 text-white rounded-full';
          }
          return null;
        }}
        tileDisabled={({ date }) => {
          // 과거 날짜 비활성화
          return date < new Date();
        }}
        minDate={new Date()}
        maxDate={new Date(new Date().setMonth(new Date().getMonth() + 3))}
      />
    </div>
  );
}

3. react-datepicker: 강력한 날짜 선택기

3.1 설치 및 기본 사용

npm install react-datepicker
npm install --save-dev @types/react-datepicker
'use client';

import { useState } from 'react';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';

export default function DatePickerExample() {
  const [startDate, setStartDate] = useState(new Date());

  return (
    <div className="p-4">
      <DatePicker
        selected={startDate}
        onChange={(date) => setStartDate(date)}
        dateFormat="yyyy-MM-dd"
        className="px-4 py-2 border rounded-lg"
      />
    </div>
  );
}

3.2 날짜 범위 선택

'use client';

import { useState } from 'react';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';

export default function DateRangePicker() {
  const [startDate, setStartDate] = useState(null);
  const [endDate, setEndDate] = useState(null);

  return (
    <div className="p-4 space-y-4">
      <div>
        <label className="block mb-2 font-medium">시작일</label>
        <DatePicker
          selected={startDate}
          onChange={(date) => setStartDate(date)}
          selectsStart
          startDate={startDate}
          endDate={endDate}
          dateFormat="yyyy-MM-dd"
          className="px-4 py-2 border rounded-lg w-full"
        />
      </div>
      <div>
        <label className="block mb-2 font-medium">종료일</label>
        <DatePicker
          selected={endDate}
          onChange={(date) => setEndDate(date)}
          selectsEnd
          startDate={startDate}
          endDate={endDate}
          minDate={startDate}
          dateFormat="yyyy-MM-dd"
          className="px-4 py-2 border rounded-lg w-full"
        />
      </div>
    </div>
  );
}

3.3 시간 선택 포함

'use client';

import { useState } from 'react';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';

export default function DateTimePicker() {
  const [startDate, setStartDate] = useState(new Date());

  return (
    <div className="p-4">
      <DatePicker
        selected={startDate}
        onChange={(date) => setStartDate(date)}
        showTimeSelect
        timeFormat="HH:mm"
        timeIntervals={15}
        dateFormat="yyyy-MM-dd HH:mm"
        className="px-4 py-2 border rounded-lg"
      />
    </div>
  );
}

4. react-day-picker: 접근성 중심 달력

4.1 설치 및 기본 사용

npm install react-day-picker
'use client';

import { useState } from 'react';
import { DayPicker } from 'react-day-picker';
import 'react-day-picker/dist/style.css';

export default function DayPickerExample() {
  const [selected, setSelected] = useState();

  return (
    <div className="flex flex-col items-center p-4">
      <DayPicker
        mode="single"
        selected={selected}
        onSelect={setSelected}
        className="rounded-lg border p-4"
      />
      {selected && (
        <p className="mt-4">선택한 날짜: {selected.toLocaleDateString()}</p>
      )}
    </div>
  );
}

4.2 다중 날짜 선택

'use client';

import { useState } from 'react';
import { DayPicker } from 'react-day-picker';
import 'react-day-picker/dist/style.css';

export default function MultiSelectCalendar() {
  const [selectedDays, setSelectedDays] = useState([]);

  return (
    <div className="flex flex-col items-center p-4">
      <DayPicker
        mode="multiple"
        selected={selectedDays}
        onSelect={setSelectedDays}
        className="rounded-lg border p-4"
      />
      <div className="mt-4">
        <p className="font-medium">선택한 날짜:</p>
        <ul className="list-disc">
          {selectedDays.map((day) => (
            <li key={day.toISOString()}>
              {day.toLocaleDateString()}
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

5. date-fns: 날짜 유틸리티 라이브러리

5.1 날짜 포맷팅과 계산

npm install date-fns
'use client';

import { useState } from 'react';
import { format, addDays, differenceInDays, isBefore, isAfter } from 'date-fns';
import { ko } from 'date-fns/locale';
import Calendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';

export default function DateUtilsExample() {
  const [selectedDate, setSelectedDate] = useState(new Date());

  const formattedDate = format(selectedDate, 'yyyy년 MM월 dd일', { locale: ko });
  const nextWeek = addDays(selectedDate, 7);
  const daysFromToday = differenceInDays(selectedDate, new Date());

  return (
    <div className="p-4 space-y-4">
      <Calendar onChange={setSelectedDate} value={selectedDate} />
      
      <div className="space-y-2">
        <p>선택한 날짜: {formattedDate}</p>
        <p>7일 후: {format(nextWeek, 'yyyy-MM-dd')}</p>
        <p>오늘로부터 {daysFromToday}일 후</p>
        <p>과거 날짜인가? {isBefore(selectedDate, new Date()) ? '예' : '아니오'}</p>
      </div>
    </div>
  );
}

5.2 날짜 범위 처리

'use client';

import { useState } from 'react';
import { format, eachDayOfInterval, startOfWeek, endOfWeek } from 'date-fns';
import { ko } from 'date-fns/locale';

export default function DateRangeUtils() {
  const [startDate, setStartDate] = useState(new Date());
  const [endDate, setEndDate] = useState(addDays(new Date(), 7));

  const daysInRange = eachDayOfInterval({
    start: startDate,
    end: endDate,
  });

  const weekStart = startOfWeek(startDate, { locale: ko });
  const weekEnd = endOfWeek(startDate, { locale: ko });

  return (
    <div className="p-4 space-y-4">
      <div>
        <p>시작일: {format(startDate, 'yyyy-MM-dd')}</p>
        <p>종료일: {format(endDate, 'yyyy-MM-dd')}</p>
      </div>
      
      <div>
        <p className="font-medium">범위 내 날짜들:</p>
        <ul className="list-disc pl-5">
          {daysInRange.map((day) => (
            <li key={day.toISOString()}>
              {format(day, 'yyyy-MM-dd (EEE)', { locale: ko })}
            </li>
          ))}
        </ul>
      </div>

      <div>
        <p>주 시작: {format(weekStart, 'yyyy-MM-dd')}</p>
        <p>주 종료: {format(weekEnd, 'yyyy-MM-dd')}</p>
      </div>
    </div>
  );
}

6. Next.js와 통합

6.1 동적 임포트로 번들 최적화

'use client';

import { useState } from 'react';
import dynamic from 'next/dynamic';

const Calendar = dynamic(() => import('react-calendar'), {
  ssr: false,
  loading: () => <div className="h-64 bg-gray-100 rounded animate-pulse" />,
});

export default function OptimizedCalendar() {
  const [date, setDate] = useState(new Date());

  return (
    <div className="p-4">
      <Calendar onChange={setDate} value={date} />
    </div>
  );
}

6.2 서버 컴포넌트와 클라이언트 컴포넌트 분리

// app/calendar/page.js (서버 컴포넌트)
import CalendarClient from './CalendarClient';

export default function CalendarPage() {
  return (
    <div className="container mx-auto p-4">
      <h1 className="text-3xl font-bold mb-4">달력</h1>
      <CalendarClient />
    </div>
  );
}
// app/calendar/CalendarClient.js (클라이언트 컴포넌트)
'use client';

import { useState } from 'react';
import Calendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';

export default function CalendarClient() {
  const [date, setDate] = useState(new Date());

  return <Calendar onChange={setDate} value={date} />;
}

7. Tailwind CSS 스타일링

7.1 react-calendar 스타일링

'use client';

import { useState } from 'react';
import Calendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';

export default function StyledCalendar() {
  const [date, setDate] = useState(new Date());

  return (
    <div className="p-4">
      <Calendar
        onChange={setDate}
        value={date}
        className="!border-2 !border-gray-300 !rounded-lg !p-4"
        tileClassName="hover:!bg-blue-100 !rounded"
        activeStartDate={date}
      />
      <style jsx global>{`
        .react-calendar {
          font-family: inherit;
        }
        .react-calendar__tile--active {
          background: #3b82f6;
          color: white;
        }
        .react-calendar__tile--now {
          background: #dbeafe;
        }
      `}</style>
    </div>
  );
}

7.2 커스텀 스타일 컴포넌트

'use client';

import { useState } from 'react';
import Calendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';

export default function CustomStyledCalendar() {
  const [date, setDate] = useState(new Date());

  return (
    <div className="p-4">
      <div className="bg-white rounded-xl shadow-lg p-6 max-w-md mx-auto">
        <Calendar
          onChange={setDate}
          value={date}
          className="!border-0 !w-full"
        />
      </div>
    </div>
  );
}

8. 실전 예제: 예약 시스템 달력

8.1 예약 가능한 날짜만 표시

'use client';

import { useState } from 'react';
import Calendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';

export default function BookingCalendar() {
  const [selectedDate, setSelectedDate] = useState(null);
  
  // 예약 가능한 날짜 (실제로는 API에서 가져옴)
  const availableDates = [
    new Date(2025, 4, 15),
    new Date(2025, 4, 16),
    new Date(2025, 4, 20),
    new Date(2025, 4, 25),
  ];

  const isDateAvailable = (date) => {
    return availableDates.some(
      (availableDate) =>
        date.toDateString() === availableDate.toDateString()
    );
  };

  return (
    <div className="p-4 max-w-md mx-auto">
      <h2 className="text-2xl font-bold mb-4">예약 날짜 선택</h2>
      
      <Calendar
        onChange={setSelectedDate}
        value={selectedDate}
        tileDisabled={({ date }) => !isDateAvailable(date)}
        tileClassName={({ date }) => {
          if (isDateAvailable(date)) {
            return 'bg-green-100 text-green-800 font-semibold';
          }
          return 'opacity-50';
        }}
        minDate={new Date()}
      />

      {selectedDate && (
        <div className="mt-4 p-4 bg-blue-50 rounded-lg">
          <p className="font-semibold">선택한 날짜:</p>
          <p>{selectedDate.toLocaleDateString('ko-KR')}</p>
        </div>
      )}
    </div>
  );
}

9. 접근성 고려사항

9.1 키보드 네비게이션

'use client';

import { useState } from 'react';
import Calendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';

export default function AccessibleCalendar() {
  const [date, setDate] = useState(new Date());

  return (
    <div className="p-4">
      <div role="application" aria-label="달력">
        <Calendar
          onChange={setDate}
          value={date}
          nextLabel="다음 달"
          prevLabel="이전 달"
          next2Label="다음 해"
          prev2Label="이전 해"
        />
      </div>
    </div>
  );
}

10. 성능 최적화

10.1 메모이제이션

'use client';

import { useState, useMemo, useCallback } from 'react';
import Calendar from 'react-calendar';
import 'react-calendar/dist/Calendar.css';

export default function OptimizedCalendar() {
  const [date, setDate] = useState(new Date());

  const handleDateChange = useCallback((newDate) => {
    setDate(newDate);
  }, []);

  const tileClassName = useCallback(({ date, view }) => {
    if (view === 'month') {
      if (date.toDateString() === new Date().toDateString()) {
        return 'bg-blue-500 text-white';
      }
    }
    return null;
  }, []);

  return (
    <div className="p-4">
      <Calendar
        onChange={handleDateChange}
        value={date}
        tileClassName={tileClassName}
      />
    </div>
  );
}

11. 결론

React 달력 라이브러리는 각각의 특징과 장단점이 있습니다. 프로젝트의 요구사항에 따라 적절한 라이브러리를 선택하는 것이 중요합니다.

선택 가이드:

  • 간단한 날짜 선택: react-calendar
  • 복잡한 날짜 입력: react-datepicker
  • 접근성 중요: react-day-picker
  • 날짜 계산: date-fns

Next.js와 함께 사용할 때는 동적 임포트를 활용하여 번들 크기를 최적화하고, 서버 컴포넌트와 클라이언트 컴포넌트를 적절히 분리하여 성능을 향상시킬 수 있습니다. 또한 Tailwind CSS와 함께 사용하면 일관된 디자인을 쉽게 구현할 수 있습니다.