Renaud is Not Bill Gates
Renaud is Not Bill Gates

Reputation: 2084

Create DateRangePicker using two MUI X DatePicker

I'm trying to build a DateRangePicker using MUI RangePickers, but I don't know how to use one only calendar to select the start date and the end date.

This is what I tried:

https://codesandbox.io/s/competent-wescoff-759vfm?file=/src/App.tsx

I'm not using the DateRangePicker from mui-x-pro, since it's only available in pro plan.

How can I make the two inputs to use one single calendar to select a range ?

Upvotes: 3

Views: 13637

Answers (2)

Harshith Kumar A
Harshith Kumar A

Reputation: 11

I know this is a very old query, for solving this issue i found a package,

https://www.npmjs.com/package/react-quick-date-range-picker

see demo: https://stackblitz.com/edit/vitejs-vite-zrmeum?file=src%2Findex.css

Or you can use the code from https://github.com/HarshithKumar-A/react-quick-date-range-picker

import React from "react";
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import {
  DatePicker,
  DatePickerProps,
  PickerValidDate,
} from "@mui/x-date-pickers";
import moment, { Moment } from "moment";
import { useEffect, useState } from "react";
import CustomCalendarHeader from "./components/CustomCalendarHeader";
import Layout from "./components/CustomCalenderLayout";
import Day from "./components/CustomCalenderDay";
import { DateRangePickerStyled } from "./styled";

moment.updateLocale("en", {
  week: {
    dow: 1,
  },
});

export type DateRange = [Moment | null, Moment | null];

interface DateRangePickerProps
  extends Omit<
    DatePickerProps<PickerValidDate, boolean>,
    "onChange" | "value"
  > {
  value: DateRange | null;
  onChange: (value: DateRange | null) => void;
}

const CustomDatePicker = ({
  value,
  onChange,
  ...restProps
}: DateRangePickerProps) => {
  const [startDate, setStartDate] = useState<Moment | null>(value?.[0] || null);
  const [endDate, setEndDate] = useState<Moment | null>(value?.[1] || null);
  const [open, setOpen] = useState(false);

  const isInRange = (date: Moment): boolean => {
    if (!startDate || !endDate) return false;
    return date.isBetween(startDate, endDate, "day", "[]");
  };

  const selectAndCloseCalendar = (start: Moment | null, end: Moment | null) => {
    if (start && !end) {
      end = start.clone();
    }
    onChange([start, end]);
    setOpen(false);
  };

  const handleToolbarAction = (
    start: Moment | null,
    end: Moment | null,
    action: string
  ) => {
    setStartDate(start);
    setEndDate(end);
    if (action !== "reset") {
      selectAndCloseCalendar(start, end);
    }
  };

  const handleDateChange = (date: Moment | null) => {
    if (!startDate || endDate || (date && date.isBefore(startDate, "day"))) {
      setStartDate(date);
      setEndDate(null);
    } else {
      setEndDate(date);
      selectAndCloseCalendar(startDate, date);
    }
  };

  useEffect(() => {
    if (value) {
      setStartDate(value[0]);
      setEndDate(value[1]);
    }
  }, [value]);

  return (
    <DateRangePickerStyled>
      <LocalizationProvider dateAdapter={AdapterMoment}>
        <DatePicker
          views={["month", "year", "day"]}
          reduceAnimations
          value={endDate || startDate || null}
          closeOnSelect={false}
          disableHighlightToday
          open={open}
          onOpen={() => setOpen(true)}
          onClose={() => selectAndCloseCalendar(startDate, endDate)}
          showDaysOutsideCurrentMonth
          dayOfWeekFormatter={(day) =>
            ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"][day.day()]
          }
          slots={{
            day: (day) => (
              <Day
                isInRange={isInRange}
                startDate={startDate}
                endDate={endDate}
                onDateClick={handleDateChange}
                {...day}
              />
            ),
            calendarHeader: (props) => (
              <CustomCalendarHeader
                date={props.currentMonth}
                onMonthChange={props.onMonthChange}
                onViewChange={props.onViewChange}
              />
            ),
            layout: (prop) => (
              <Layout
                handleToolbarAction={handleToolbarAction}
                startDate={startDate}
                endDate={endDate}
              >
                {prop.children}
              </Layout>
            ),
          }}
          {...restProps}
        />
      </LocalizationProvider>
    </DateRangePickerStyled>
  );
};

export default CustomDatePicker;

Upvotes: 0

LazyJ
LazyJ

Reputation: 601

If I understand you correctly you want one calendar where you can press a start date and an end date and then these are highlighted. I have solved this but I haven't done much about the design so that will be up to you. I have solved the problem using the DatePicker elements renderDay property.

import * as React from 'react';
import TextField, { TextFieldProps } from '@mui/material/TextField';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DatePicker, DatePickerProps } from '@mui/x-date-pickers/DatePicker';
import styled from '@emotion/styled';
import { PickersDay } from '@mui/x-date-pickers/PickersDay';

const DateRangePickerStyled = styled('div')(() => ({
    display: 'flex',
    alignItems: 'center',
}));

const DateRangePicker = (props: DateRangePickerProps) => {
    const { value, onChange, ...rest } = props;
    const [startDate, setStartDate] = React.useState(0);
    const [endDate, setEndDate] = React.useState(0);
    const [datesPicked, setDatesPicked] = React.useState(0);

    return (
        <DateRangePickerStyled>
            <DatePicker
                value={new Date()}
                minDate={startDate}
                onChange={(date: any) => {
                    setDatesPicked(datesPicked + 1);
                    if (datesPicked % 2 !== 0) {
                        setEndDate(date.$D);
                    } else {
                        setStartDate(date.$D);
                        setEndDate(0);
                    }
                }}
                closeOnSelect={false}
                renderDay={(day, _value, DayComponentProps) => {
                    const isSelected =
                        !DayComponentProps.outsideCurrentMonth &&
                        Array.from(
                            { length: endDate - startDate + 2 },
                            (x, i) => i + startDate - 1
                        ).indexOf(day.date()) > 0;
                    return (
                        <div
                            style={
                                isSelected
                                    ? {
                                            backgroundColor: 'blue',
                                      }
                                    : {}
                            }
                            key={day.toString()}
                        >
                            <PickersDay {...DayComponentProps} />
                        </div>
                    );
                }}
                {...rest}
            />
        </DateRangePickerStyled>
    );
};

export default function MaterialUIPickers() {
    const [value, setValue] = React.useState<DateRangePickerValueType | null>(
        null
    );

    return (
        <LocalizationProvider dateAdapter={AdapterDayjs}>
            <DateRangePicker
                value={value}
                onChange={(newValue) => {
                    setValue(newValue as DateRangePickerValueType);
                }}
                renderInput={(params: TextFieldProps) => (
                    <TextField {...params} />
                )}
            />
        </LocalizationProvider>
    );
}

type DateRangePickerValueType = {
    start: unknown;
    end: unknown;
};

interface DateRangePickerProps
    extends Omit<DatePickerProps<unknown, unknown>, 'value'> {
    value: DateRangePickerValueType | null;
}

More examples of this method can be found on: https://mui.com/x/react-date-pickers/date-picker/ If you also want to limit which dates are selectable I would suggest using the minDate property. If I've misunderstood what it is you wanted or my solution is not all the way there feel free to leave a comment and I'll take an extra look, hope it helps :-)

Upvotes: 3

Related Questions