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).
- Is your input
<Datepicker />

- Is your floating date picker
<DatepickerFloatingItem/>

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:

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.