I recently started using material-ui in my React + TypeScript project. The code below was written based on the sample code on the official website.
import AdapterDateFns from '@mui/lab/AdapterDateFns';
import DatePicker from '@mui/lab/DatePicker';
import LocalizationProvider from '@mui/lab/LocalizationProvider';
import TextField from '@mui/material/TextField';
import React, { useState } from 'react';
const SampleDatePickerOriginal = () => {
const [date, setDate] = useState<Date>(new Date());
const onChangeHandler = (_date: Date | null, keyboardInputValue?: string) => {
if (_date) {
setDate(_date);
}
};
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
onChange={onChangeHandler}
value={date}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
);
};
export default SampleDatePickerOriginal;
I wanted to use input element instead of TextField in this code. So I referred to material-ui's issue (https://github.com/mui/material-ui-pickers/issues/1751) and wrote the code below.
import AdapterDateFns from '@mui/lab/AdapterDateFns';
import DatePicker from '@mui/lab/DatePicker';
import LocalizationProvider from '@mui/lab/LocalizationProvider';
import React, { useState } from 'react';
const SampleDatePickerCustom = () => {
const [date, setDate] = useState<Date>(new Date());
const [isOpen, setIsOpen] = useState<boolean>(false);
const onChangeHandler = (_date: Date | null, keyboardInputValue?: string) => {
if(_date){
setDate(_date);
}
};
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
onChange={onChangeHandler}
onClose={() => {setIsOpen(false);}}
open={isOpen}
value={date}
renderInput={({
ref,
inputProps,
disabled,
onChange,
value,
...other
}) => (
<div ref={ref}>
<input
style={{ display: 'none' }}
value={date.toISOString()}
onChange={onChange}
disabled={disabled}
{...inputProps}
/>
<button onClick={() => {setIsOpen(!isOpen);}}>
OPEN
</button>
</div>)} />
</LocalizationProvider>
);
};
export default SampleDatePickerCustom;
This code works fine, but the calendar that opens is in the upper left corner. How can I make it appear just below the button like when I use TextField?
I was stuck on the same problem here today and luckily got it working for me now.
The idea is that you have to pass an anchor element to the Material UI Popper component, which is basically the wrapper for your Datepicker Popup.
That means you have to create a ref object for your custom input and pass that to the popper object as anchorEl.
If you check the MaterialUI X documentation for the Datepicker API, you can see it under the Props section as PropperProps.
Your code then would look something like this:
import React, { useState, useRef } from 'react';
const SampleDatePickerCustom = () => {
const [date, setDate] = useState<Date>(new Date());
const [isOpen, setIsOpen] = useState<boolean>(false);
const customInputRef = useRef();
const onChangeHandler = (_date: Date | null, keyboardInputValue?: string) => {
if(_date){
setDate(_date);
}
};
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
onChange={onChangeHandler}
onClose={() => {setIsOpen(false);}}
open={isOpen}
value={date}
PopperProps={{ anchorEl: customInputRef.current }}
renderInput={({
ref,
inputProps,
disabled,
onChange,
value,
...other
}) => (
<div ref={ref}>
<input
style={{ display: 'none' }}
value={date.toISOString()}
onChange={onChange}
disabled={disabled}
ref={customInputRef}
{...inputProps}
/>
<button onClick={() => {setIsOpen(!isOpen);}}>
OPEN
</button>
</div>)} />
</LocalizationProvider>
);
};
The only things that I added are the customInputRef object, which is assigned as a ref to your input and the PropperProps object on your Datepicker, which only has one property, the customInputRef.current element.
That should make the Popper be anchored at the provided custom input element.
I didn't type the additions, since I'm not the best at TS, but I hope it's enough to understand how it works :)
I was able to work around it by using TextField but hiding it with styles...
<TextField style={{ opacity: 0, width: 0, height: 0 }} {...params} />
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
open={calendarOpen}
onClose={() => setCalendarOpen(false)}
value={date}
onChange={(newValue) => setDate(newValue)}
renderInput={(params) => (
<div>
{/* HACK: this next line was needed to get the calendar to render in the right position */}
<TextField style={{ opacity: 0, width: 0, height: 0 }} {...params} />
<Button
className="p-1"
variant="outlined"
onClick={() => setCalendarOpen((calendarOpen) => !calendarOpen)}>
<CalendarIcon />
</Button>
</div>
)}
/>
</LocalizationProvider>