Search code examples
reactjstypescriptnext.jstailwind-cssreact-calendar

NextJS react-calendar duplicate dates selection issue


I'm developing a calendar component using NextJS, typescript, tailwindcss, react-calendar library and I ran into a problem with duplicate dates in the calendar when selecting a date range. I added exceptions for duplicate dates elsewhere, but I couldn't remove duplicate dates in the next month - all dates of the month are duplicated except for the selected dates, and except for the dates of another month.

Also there is a problem with inability to select current day, month, year in the calendar.

Kindly help to understand and fix this problem.

https://codesandbox.io/p/sandbox/young-hill-63l7v7

initial selected-dates duplicates

import React, { useState } from 'react';
import Calendar from 'react-calendar';
import ReservationModal from './ReservationModal';

import '../calendar/CusCalendar.css';

const CusCalendar: React.FC = () => {
  const [selectedDates, setSelectedDates] = useState<Date[] | Date | null>(null);
  const [showCalculation, setShowCalculation] = useState(false);
  const [showReservationPopup, setShowReservationPopup] = useState(false);
  const [currentMonth, setCurrentMonth] = useState(new Date());

  const startDate = Array.isArray(selectedDates) ? selectedDates[0] : null;
  const endDate = Array.isArray(selectedDates) ? selectedDates[1] : null;
  const pricePerNight = 100;

  const nights = startDate && endDate ? Math.floor((endDate.getTime() - startDate.getTime()) / (1000 * 3600 * 24)) : 0;
  const totalPrice = nights * pricePerNight;

  const isDateSelected = (date: Date) => {
    return startDate && endDate && date >= startDate && date <= endDate;
  };

  const handleDateChange = (date: Date | Date[]) => {
    if (!startDate || (startDate && endDate)) {
      setSelectedDates(Array.isArray(date) ? date : [date]);
    } else {
      if (Array.isArray(date) && date[0] >= startDate) {
        setSelectedDates(date);
      } else {
        setSelectedDates(Array.isArray(date) ? date : [date]);
      }
    }
  };

  const handleCalculate = () => {
    if (startDate && endDate) {
      const numberOfNights = Math.floor(
        (endDate.getTime() - startDate.getTime()) / (1000 * 3600 * 24)
      );
      const totalPrice = numberOfNights * pricePerNight;
      setShowCalculation(true);
    }
  };

  const handleClearDates = () => {
    setSelectedDates(null);
  };

  const handleReserve = () => {
    if (startDate && endDate) {
      setShowReservationPopup(true);
    }
  };

  const handleReserveClick = () => {
    setShowReservationPopup(true);
  };

  const handleMonthChange = (newMonth: Date) => {
    setCurrentMonth(newMonth);
  };

  return (
    <div className="text-start border-4 rounded-[30px] solid border-[#ebebeb] w-[478px] h-full">
      {!showCalculation ? (
        <div>
          <div className="text-[28px] font-bold text-[#313131] leading-normal uppercase pl-7 pt-7 pb-3">SELECT CHECK-IN DATE</div>
          <div className="text-[18px] font-normal leading-normal text-[#969696] pl-7 pb-7">Add your travel dates for exact pricing</div>
        </div>
      ) : null}
      <div className="mx-7">
        {!showCalculation ? (
          <Calendar
            onChange={handleDateChange as any}
            selectRange={true}
            tileContent={({ date, view }) => {
              if (view === 'month') {
                if (selectedDates === null || isDateSelected(date)) {
                  return null;
                } else if (date < new Date()) {
                  return null;
                } else if (
                  (startDate && endDate) &&
                  (date.getMonth() !== startDate.getMonth() || date.getMonth() !== endDate.getMonth())
                ) {
                  return null;
                } else {
                  return (
                    <div className="">
                      {date.getDate()}
                    </div>
                  );
                }
              }
              return null;
            }}
            tileDisabled={({ date }) => date <= new Date()}
          />
        ) : null}
      </div>
...

Solution

  • Removing the else clause in CusCalendar.tsx:102 seems to solve the problem1.

    However, it's up to you to determine if this has other implications in your project, the business logic is not entire clear (at least to me).


    1 - Upon further inspection, I noticed you're returning null for all of those conditions and it appears removing tileContent altogether fixes the problem. I don't fully understand what you're trying to achieve there, but that's clearly the root cause of the bug.