Lluís Mañas
Lluís Mañas

Reputation: 53

How to disable dates in react-day-picker for already booked dates?

I'm working on a booking widget using react-day-picker in a React application. I need to disable dates that are already booked so users cannot select them. I've fetched the booked dates from my backend, but I'm having trouble disabling these dates in the calendar.

The DayPicker should disable already booked dates and past dates, but it's not working as expected, since i can select all future dates

Error Message: N/A

Environment:

React: 17.0.2 react-day-picker: 8.0.0 axios: 0.21.1

How can I ensure the calendar correctly disables unavailable dates and handles date selection accurately?

Here's my current component:

import { useContext, useEffect, useState } from "react";
import { differenceInCalendarDays, isSameDay } from "date-fns";
import axios from "axios";
import { Navigate, useParams } from "react-router-dom";
import { UserContext } from "../UserContext";
import { DayPicker } from "react-day-picker";
import "react-day-picker/dist/style.css";

function BookingWidget({ place }) {
  const [checkIn, setCheckIn] = useState("");
  const [checkOut, setCheckOut] = useState("");
  const [numberGuests, setNumberGuests] = useState(1);
  const [name, setName] = useState("");
  const [mobile, setMobile] = useState("");
  const [redirect, setRedirect] = useState("");
  const { user } = useContext(UserContext);
  const { id } = useParams();
  const [alreadyBooked, setAlreadyBooked] = useState([]);

  useEffect(() => {
    if (user) {
      setName(user.name);
    }
    axios.get("/booking/" + id).then((response) => {
      const bookings = response.data;
      const bookedDates = [];
      for (let booking of bookings) {
        const start = new Date(booking.checkIn);
        const end = new Date(booking.checkOut);
        for (let date = start; date <= end; date.setDate(date.getDate() + 1)) {
          bookedDates.push(new Date(date));
        }
      }
      setAlreadyBooked(bookedDates);
    });
  }, [user, id]);

  let numberOfNights = 0;
  if (checkIn && checkOut) {
    numberOfNights = differenceInCalendarDays(
      new Date(checkOut),
      new Date(checkIn)
    );
  }

  async function bookThisPlace() {
    const response = await axios.post("/bookings", {
      checkIn,
      checkOut,
      numberGuests,
      name,
      mobile,
      price: numberOfNights * place.price,
      place: place._id,
    });
    const bookingId = response.data._id;
    setRedirect(`/account/bookings/${bookingId}`);
  }

  const isPastDay = (day) => {
    return day < new Date();
  };

  const isBookedDay = (day) => {
    return alreadyBooked.some(bookedDay => isSameDay(day, bookedDay));
  };

  const disabledDays = [
    { before: new Date() },  // Disable past days
    ...alreadyBooked.map(date => ({ date })) // Disable already booked days
  ];

  if (redirect) {
    return <Navigate to={redirect} />;
  }

  return (
    <div className="bg-white shadow p-4 rounded-2xl">
      <div className="text-2xl text-center mb-3">
        <b>Price: {place.price} €/night</b>
      </div>
      <div className="border mt-4 rounded-2xl">
        <div className="flex">
          <div className="py-3 px-4">
            <label>Check-in date: </label>
            <DayPicker
              mode="single"
              selected={checkIn ? new Date(checkIn) : undefined}
              onSelect={setCheckIn}
              disabled={disabledDays}
            />
          </div>
          <div className="py-3 px-4 border-l">
            <label>Check-out date: </label>
            <DayPicker
              mode="single"
              selected={checkOut ? new Date(checkOut) : undefined}
              onSelect={setCheckOut}
              disabled={disabledDays}
            />
          </div>
        </div>
        <div className="py-3 px-4 border-t">
          <label>Guests: </label>
          <input
            type="number"
            value={numberGuests}
            onChange={(ev) => setNumberGuests(ev.target.value)}
          />
        </div>
        {numberOfNights > 0 && (
          <div className="py-3 px-4 border-t">
            <label>Your name: </label>
            <input
              type="text"
              value={name}
              onChange={(ev) => setName(ev.target.value)}
            />
            <label>Phone: </label>
            <input
              type="tel"
              value={mobile}
              onChange={(ev) => setMobile(ev.target.value)}
            />
          </div>
        )}
      </div>

      <button onClick={bookThisPlace} className="primary rounded-2xl mt-4">
        Book now{" "}
        {numberOfNights > 0 && (
          <span>{numberOfNights * place.price} €</span>
        )}
      </button>
    </div>
  );
}

export default BookingWidget;

´´´

Upvotes: 0

Views: 75

Answers (1)

Rahul Rajput
Rahul Rajput

Reputation: 1

To ensure the calendar correctly disables unavailable dates and handles date selection accurately, you need to address a few issues in your BookingWidget component:

Correctly Format Disabled Days: react-day-picker requires disabled days to be specified in a specific format. You need to map your alreadyBooked dates to the correct format for disabling.

Handling Date Selection: Ensure the onSelect handlers for DayPicker are correctly updating the state.

Here's the updated component with corrections:

import { useContext, useEffect, useState } from "react";
import { differenceInCalendarDays, isSameDay } from "date-fns";
import axios from "axios";
import { Navigate, useParams } from "react-router-dom";
import { UserContext } from "../UserContext";
import { DayPicker } from "react-day-picker";
import "react-day-picker/dist/style.css";

function BookingWidget({ place }) {
  const [checkIn, setCheckIn] = useState("");
  const [checkOut, setCheckOut] = useState("");
  const [numberGuests, setNumberGuests] = useState(1);
  const [name, setName] = useState("");
  const [mobile, setMobile] = useState("");
  const [redirect, setRedirect] = useState("");
  const { user } = useContext(UserContext);
  const { id } = useParams();
  const [alreadyBooked, setAlreadyBooked] = useState([]);

  useEffect(() => {
    if (user) {
      setName(user.name);
    }
    axios.get("/booking/" + id).then((response) => {
      const bookings = response.data;
      const bookedDates = [];
      for (let booking of bookings) {
        const start = new Date(booking.checkIn);
        const end = new Date(booking.checkOut);
        for (let date = start; date <= end; date.setDate(date.getDate() + 1)) {
          bookedDates.push(new Date(date));
        }
      }
      setAlreadyBooked(bookedDates);
    });
  }, [user, id]);

  let numberOfNights = 0;
  if (checkIn && checkOut) {
    numberOfNights = differenceInCalendarDays(
      new Date(checkOut),
      new Date(checkIn)
    );
  }

  async function bookThisPlace() {
    const response = await axios.post("/bookings", {
      checkIn,
      checkOut,
      numberGuests,
      name,
      mobile,
      price: numberOfNights * place.price,
      place: place._id,
    });
    const bookingId = response.data._id;
    setRedirect(`/account/bookings/${bookingId}`);
  }

  const disabledDays = [
    { before: new Date() },  // Disable past days
    ...alreadyBooked.map(date => ({ date })) // Disable already booked days
  ];

  if (redirect) {
    return <Navigate to={redirect} />;
  }

  return (
    <div className="bg-white shadow p-4 rounded-2xl">
      <div className="text-2xl text-center mb-3">
        <b>Price: {place.price} €/night</b>
      </div>
      <div className="border mt-4 rounded-2xl">
        <div className="flex">
          <div className="py-3 px-4">
            <label>Check-in date: </label>
            <DayPicker
              mode="single"
              selected={checkIn ? new Date(checkIn) : undefined}
              onSelect={(date) => setCheckIn(date)}
              disabled={disabledDays}
            />
          </div>
          <div className="py-3 px-4 border-l">
            <label>Check-out date: </label>
            <DayPicker
              mode="single"
              selected={checkOut ? new Date(checkOut) : undefined}
              onSelect={(date) => setCheckOut(date)}
              disabled={disabledDays}
            />
          </div>
        </div>
        <div className="py-3 px-4 border-t">
          <label>Guests: </label>
          <input
            type="number"
            value={numberGuests}
            onChange={(ev) => setNumberGuests(ev.target.value)}
          />
        </div>
        {numberOfNights > 0 && (
          <div className="py-3 px-4 border-t">
            <label>Your name: </label>
            <input
              type="text"
              value={name}
              onChange={(ev) => setName(ev.target.value)}
            />
            <label>Phone: </label>
            <input
              type="tel"
              value={mobile}
              onChange={(ev) => setMobile(ev.target.value)}
            />
          </div>
        )}
      </div>

      <button onClick={bookThisPlace} className="primary rounded-2xl mt-4">
        Book now{" "}
        {numberOfNights > 0 && (
          <span>{numberOfNights * place.price} €</span>
        )}
      </button>
    </div>
  );
}

export default BookingWidget;

Upvotes: 0

Related Questions