2

How to get modal to auto-expand when react-datepicker is clicked? Modal fits initially, but then when you click on the date field and the react-datepicker shows the calendar, the react-modal dialog does not auto-expand?

Before: enter image description here

Ater clicking date: enter image description here

Code:

import React, { useState } from 'react';
import Modal from 'react-modal';
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";

const customStyles = {
  content: {
    top: '50%',
    left: '50%',
    right: 'auto',
    bottom: 'auto',
    marginRight: '-50%',
    transform: 'translate(-50%, -50%)',
  },
};

export default function TestPage2() {
  const [modalIsOpen, setIsOpen] = React.useState(false);
  const [startDate, setStartDate] = useState(new Date());

  function openModal() {
    setIsOpen(true);
  }

  function closeModal() {
    setIsOpen(false);
  }

  return (
    <div>
      <button onClick={openModal}>Open Modal</button>
      <Modal
        isOpen={modalIsOpen}
        onRequestClose={closeModal}
        contentLabel="Example Modal"
        style={customStyles}
      >
        <h3>Prior to Date Picker</h3>
        <label htmlFor="Checkin Date">Checkin Date</label>
        <DatePicker
          selected={startDate}
          // wrapperClassName="datePicker"
          // className="form-control"
          dateFormat="d MMMM yyyy"
          name="checkinDate"
          onChange={(date, event) => {
            if (date) {
              setStartDate(date)
            }
          }}
          />
          <h3>After Date Picker</h3>
      </Modal>
    </div>
  );
}
Greg
  • 34,042
  • 79
  • 253
  • 454

2 Answers2

2

I exactly faced the same issue a few weeks ago. I was looking for a easy fix but I' didn't found one. What I did is to split up the Datepicker into 2 components (+ redux).

  1. Is your input <Datepicker />

enter image description here

  1. Is your floating date picker <DatepickerFloatingItem/>

enter image description here

Datepicker

The datepicker is simply the input field and that's the component you can use throughout your react webapp. The biggest change here is that you need to run an action with the dispatcher to show the floating item. Additionally you need to determine the X and Y coordinates of your Datepicker to place the Floating Item at the correct spot.

Here is how the Datepicker component could look like (I've deleted the logic code to not confuse everyone):

class DatetimePicker extends React.Component<IDatetimePickerProps, IDatetimePickerState> {

    public componentDidMount() {
        this.updateDate(this.props.date);
        window.addEventListener("resize", this.updateDimensions);
    }

    public componentWillUnmount() {
        // display the floating datepicker even when resizing the window
        window.removeEventListener("resize", this.updateDimensions);
    }

    ///
    /// LOGIC CODE (deleted)
    ///

    private showCalendar = (): void => {
        const { date, key } = this.state;
        const { dispatch } = this.props;
        const { showFloating } = this.props.datePickerState

        if (!showFloating) {
            this.createOutsideClickEvent();
            if (date) {
                this.updateDate(date);
            }
        } else {
            this.removeOutsideClickEvent();
        }

        var boundingRect = document.getElementById(key)?.getBoundingClientRect();
        if (boundingRect) {
            dispatch(updateDatepickerData({ updateDate: this.updateDate, showFloating: true, date, positionX: boundingRect.left, positionY: boundingRect.top + boundingRect.height, key }))
        }
    }

    private updateDimensions = (): void => {
        const { dispatch } = this.props;
        const { date, showFloating } = this.props.datePickerState;
        const { key } = this.state;

        var boundingRect = document.getElementById(key)?.getBoundingClientRect();
        if (boundingRect && this.props.datePickerState.key === key) {
            dispatch(updateDatepickerData({ positionX: boundingRect.left, positionY: boundingRect.top + boundingRect.height, date, showFloating }))
        }
    }


    public render(): React.ReactNode {
        const { input, wrapperRef, key } = this.state;
        const { style, disabled, className, onClick, styleWrapper, icon } = this.props;

        return <span className="datetimepicker" ref={wrapperRef} onClick={onClick} style={styleWrapper}>
            <Input
                className={`datetimepicker__input ${className}`}
                value={input}
                onChange={this.updateInput}
                getFocus={this.disableErrorView}
                getBlur={this.textInputLostFocus}
                rightButtonClicked={this.showCalendar}
                style={style}
                id={key}
                icon={icon}
                disabled={disabled}
            />
        </span>
    }
}

DatepickerFloatingItem

You only need to position the DatepickerFloatingItem once in your application. It's best to position it at App.js (the root component).

It's also important to have position: relative for the parent element and define position: fixed for the DatepickerFloatingItem. Now you can easily position your floating element using top: and left: with the coordinates of the Datepicker

And this is how the DatepickerFloatingItem could look like (I also removed the unnecessary code to keep it more understandable)

interface IDatepickerFloatingItemStateProps {
    date: Date
    showFloating: boolean
    positionX?: number
    positionY?: number
}

class DatepickerFloatingItem extends React.Component<IDatepickerFloatingItemProps, IDatepickerFloatingItemState> {

    private clickedFloatingDatepicker = (event: React.MouseEvent<HTMLSpanElement>): void => event.preventDefault()
    private updateDate = (date?: Date, closeFloating?: boolean): void => {
        const { dispatch } = this.props;

        dispatch(updateDatepickerDate(date ? date : new Date()))
        if (closeFloating) dispatch(updateDatepickerShowFloating(!closeFloating))
    }

    public render(): React.ReactNode {
        const { showFloating, date, positionX, positionY } = this.props;
        const { datepickerView } = this.state;

        return <span className={`datetimepicker__floating ${showFloating ? "show" : ""}`} onClick={this.clickedFloatingDatepicker} style={{ top: `${positionY}px`, left: `${positionX}px` }}>
            <DateCalendar datepickerView={datepickerView} date={date} updateDate={this.updateDate} />
        </span>
    }
}

function mapStateToProps(applicationState: ApplicationState): IDatepickerFloatingItemStateProps {
    return {
        date: applicationState.datepicker.date,
        showFloating: applicationState.datepicker.showFloating,
        positionX: applicationState.datepicker.positionX,
        positionY: applicationState.datepicker.positionY
    }
}

export default connect(mapStateToProps)(DatepickerFloatingItem)

Redux

I had to move some stuff to the Redux store to ensure that the FloatingDatePicker as well as the Datepicker have chance to communicate somehow

I've kept the redux store pretty straight forward:

import { Action, Reducer } from 'redux';

export interface DatepickerState {
    date: Date
    showFloating: boolean
    positionX?: number
    positionY?: number
    updateDate?: (date?: Date) => void
}

export const UPDATE_DATEPICKER_SHOWFLOATING = "UPDATE_DATEPICKER_SHOWFLOATING";
export const UPDATE_DATEPICKER_DATA = "UPDATE_DATEPICKER_DATA";
export const UPDATE_DATEPICKER_DATE = "UPDATE_DATEPICKER_DATE";

export interface UpdateDatepickerShowFloating {
    type: "UPDATE_DATEPICKER_SHOWFLOATING"
    showFloating: boolean
}

export interface UpdateDatepickerDate {
    type: "UPDATE_DATEPICKER_DATE"
    date: Date
}

export interface UpdateDatepickerData {
    type: "UPDATE_DATEPICKER_DATA"
    state: DatepickerState
}

type KnownAction = UpdateDatepickerShowFloating | UpdateDatepickerData | UpdateDatepickerDate

const unloadedState: DatepickerState = { updateDate: () => { }, date: new Date(), showFloating: false, showTime: false, positionX: 0, positionY: 0 }

export const reducer: Reducer<DatepickerState> = (state: DatepickerState | undefined, incomingAction: Action): DatepickerState => {

    if (state === undefined) {
        return unloadedState;
    }

    const action = incomingAction as KnownAction;

    switch (action.type) {
        case UPDATE_DATEPICKER_SHOWFLOATING:
            return { ...state, showFloating: action.showFloating }
        case UPDATE_DATEPICKER_DATE:
            setTimeout(() => { if (state.updateDate) state.updateDate(action.date) }, 1)
            return { ...state, date: action.date }
        case UPDATE_DATEPICKER_DATA:
            return { ...state, ...action.state }
        default:
            break;
    }

    return state;
}

And as you can see at the image, it's actually working inside a modal: enter image description here

I know this approach is pretty time consuming, but still I hope it was still helping you somehow and I also hope your eyes don't burn from seeing class based components.

Athii
  • 559
  • 5
  • 19
2

add the following property to your content object inside customStyles:

 overflow: 'hidden'

and change the property values of react-datepicker-popper class :

.react-datepicker-popper {
  position: static!important;
  transform: none!important;
}

codesandbox: https://codesandbox.io/s/awesome-feather-mj81tz

Omid Poorali
  • 169
  • 4