Disable manual input for MUI TimePicker - reactjs

I have a bit of a custom TimePicker provided from Material UI. I add an ability for the user to select only whole hours, such as 15:00, 16:00 etc. by clicking on a clock icon
What I want to achieve is to add same for manual input of the text field. For now user can manually add any valid time, for example 14:34, which is incorrect for my case
Can anyone help me?
Here is my TimePicker:
<LocalizationProvider dateAdapter={AdapterDateFns} locale={locale}>
<TimePicker
ampm={false}
openTo="hours"
views={['hours']}
inputFormat="HH:mm"
mask="__:__"
value={dayStartValue}
InputAdornmentProps={{
position: 'start',
}}
components={{
OpenPickerIcon: ClockFilledIcon,
}}
onChange={(newValue) => {
setDayStartValue(newValue)
}}
renderInput={(params) =>
<TextField
{...params}
helperText="Input start of Day time"
/>
}
/>
</LocalizationProvider>

You can control and validate the user's input when he clicks away from the time picker by using onBlur inside InputProps.
import * as React from "react";
import TextField from "#mui/material/TextField";
import { AdapterDateFns } from "#mui/x-date-pickers/AdapterDateFns";
import { LocalizationProvider } from "#mui/x-date-pickers/LocalizationProvider";
import { TimePicker } from "#mui/x-date-pickers/TimePicker";
export default function BasicTimePicker() {
const [dayStartValue, setDayStartValue] = React.useState<Date | null>(null);
return (
<LocalizationProvider
dateAdapter={AdapterDateFns}
locale={locale}
>
<TimePicker
ampm={false}
openTo="hours"
views={["hours"]}
inputFormat="HH:mm"
mask="__:__"
value={dayStartValue}
InputAdornmentProps={{
position: "start"
}}
components={{
OpenPickerIcon: ClockFilledIcon,
}}
onChange={(newValue: Date, keyboardInputValue?: string) => {
setDayStartValue(newValue);
}}
renderInput={(params) => (
<TextField {...params} helperText="Input start of Day time" />
)}
InputProps={{
onBlur: (
e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement, Element>
) => {
const newDate = new Date(dayStartValue);
newDate.setMinutes(0);
if (e.target.value && e.target.value.length === 5) {
setDayStartValue(newDate);
}
}
}}
/>
</LocalizationProvider>
);
}
For the validation we check the user input and if it's a valid Date (5 characters length), we create a new Date with that and set the minutes to 0.
Code Sandbox example

You can change your input format and the mask to only accept whole hours like so:
<LocalizationProvider dateAdapter={AdapterDayjs}>
<TimePicker
ampm={false}
openTo="hours"
views={['hours']}
inputFormat="HH:00"
mask="__:00"
value={value}
InputAdornmentProps={{
position: 'start',
}}
onChange={(newValue) => {
setValue(newValue)
}}
renderInput={(params) =>
<TextField
{...params}
helperText="Input start of Day time"
/>
}
/>
</LocalizationProvider>
sandbox

Related

Convert DatePicker value in material ui

I have a form with a datePicker input from material-ui
When I pick a date and submit my form, if I console.log my data I will receive 'Tue Jan 10 2023 10:31:48 GMT+0100 (Central European Standard Time)' i want to received '01/11/2023'
I would like to format the data received.
Here my DatePicker :
const [deadLine, setDeadLine] = useState(new Date());
<Controller
name="deadLine"
defaultValue={deadLine}
control={control}
render={({ field: { onChange, ...restField } }) => (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
label="Request Date"
onChange={(event) => {
console.log(event);
onChange(event);
setDeadLine(event);
}}
renderInput={(params) => <TextField {...params} />}
{...restField}
/>
</LocalizationProvider>
)}
/>
I have already tried :
To Use "format="dd-MM-yyyy"" from material-ui, but it only change the
visual not the data received.
To use moment with
setDeadLine(moment(event.target.value).format('MM/DD/YYYY'));,
still received the initial value.
To know : I am using react-hook-form
First of all to use moment you should wrap you DatePicker with moment Adapter:
<LocalizationProvider dateAdapter={AdapterMoment}> more also, onChange return the value you shouldn't access it with event.target.value, but you have directly the value with moment(value).format('DD/MM/YYYY') and of course don't forget to import Adapter Moment - import { AdapterMoment } from '#mui/x-date-pickers/AdapterMoment';
It should look like that:
<LocalizationProvider dateAdapter={AdapterMoment}>
<DatePicker
label="Request Date"
onChange={(value) => {
setDeadLine(moment(value).format('DD/MM/YYYY'));
}}
renderInput={(params) => <TextField {...params} />}
{...restField}
/>
</LocalizationProvider>

MaterialUI / ReactJS: MobileDatePicker not closing after date select

Using closeOnSelect on this, but after selecting a date it remains open - only escaping or clicking OK or Cancel buttons does it disappear. I am using this on both desktop and mobile.
<LocalizationProvider dateAdapter={AdapterDateFns}>
<MobileDatePicker
style={{ width: '100%' }}
label={"Date From"}
inputFormat="dd/MM/yyyy"
closeOnSelect={true}
views={['day']}
value={abc}
renderInput={(params) => <TextFields {...params} fullWidth />}
/>
</LocalizationProvider>
How do I make sure this closes after selecting date?
Set disableCloseOnSelect prop to false.
<LocalizationProvider dateAdapter={AdapterDateFns}>
<MobileDatePicker
style={{ width: '100%' }}
label={"Date From"}
inputFormat="dd/MM/yyyy"
disableCloseOnSelect={false}
views={['day']}
value={abc}
renderInput={(params) => <TextFields {...params} fullWidth />}
/>
</LocalizationProvider>
Check codesandbox https://codesandbox.io/s/date-picker-material-ui-forked-so0v14
I made a slight change in your code. I hope it can help you:
you have to add required props for your date picker, e.g. {onChange, value }
datePickers won't accept style as props here
import * as React from "react";
import TextField from "#mui/material/TextField";
import { AdapterDateFns } from "#mui/x-date-pickers/AdapterDateFns";
import { LocalizationProvider } from "#mui/x-date-pickers/LocalizationProvider";
import { MobileDatePicker } from "#mui/x-date-pickers/MobileDatePicker";
export default function MaterialUIPickers() {
const [value, setValue] = React.useState<Date | null>(
new Date("2014-08-18T21:11:54")
);
const handleChange = (newValue: Date | null) => {
setValue(newValue);
};
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<MobileDatePicker
label={"Date From"}
inputFormat="dd/MM/yyyy"
value={value}
views={["day"]}
onChange={handleChange}
renderInput={(params) => <TextField {...params} fullWidth />}
/>
</LocalizationProvider>
);
}

Use mui styled for mui-x datepicker

I wanted to customize the datepicker and tried to remove the padding from the input in the mui-x datepicker but nothing was working.
Am i doing something wrong here or is styled not supported by mui-x?
import { styled } from '#mui/material/styles';
import { DesktopDatePicker, LocalizationProvider } from '#mui/x-date-pickers';
import { DatePicker } from '#mui/x-date-pickers/DatePicker';
import { AdapterDateFns } from '#mui/x-date-pickers/AdapterDateFns';
import { TextField } from '#mui/material';
const DateDisplay = styled(DesktopDatePicker)(({ theme }) => ({
'& input':{
padding: 0,
},
}));
return (
<ModalDialog>
<div>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DateDisplay
value={new Date()}
readOnly
onChange={() => {}}
renderInput={(params) => <TextField {...params} />}
></DateDisplay>
</LocalizationProvider>
</div>
</ModalDialog>
);
};
Use sx={{}} prop.
<DateDisplay
value={new Date()}
readOnly
onChange={() => {}}
renderInput={(params) => (
<TextField
{...params}
sx={{
'.MuiInputBase-input': {padding: 0},
}}
/>
)}
></DateDisplay>
With '.MuiInputBase-input' class, you can customize your <TextField> render input for your DatePicker.(You can also see it in the picture)

React and MUI: How to fix the MobileDateRangePicker start date to current date and not allow to change it?

How to fix the MobileDateRangePicker start date to current date and not allow to change it in the following code?
import { MobileDateRangePicker, LocalizationProvider } from '#mui/lab';
.....
<LocalizationProvider .....>
<MobileDateRangePicker
startText="Start"
endText="End"
.....
renderInput={(startProps, endProps) => (
<React.Fragment>
<TextField {...startProps} />
<Box sx={{ mx: 2 }}> - </Box>
<TextField {...endProps} />
</React.Fragment>
)}
/>
</LocalizationProvider>
Note: To simplify the code I used ..... notation, because in the React ... has already a meaning in the code syntax.
Start by giving the start date an initial value:
const [value, setValue] = React.useState([new Date(), null]);
Set only endDate on change:
function handleChange([newStartDate, newEndDate]) {
console.log(newEndDate);
setValue(([startDate]) => [startDate, newEndDate]);
}
...
<MobileDateRangePicker
disablePast
startText="Start"
endText="End"
value={value}
onChange={handleChange}
...
Add disabled to inputProps of the start's TextField:
<TextField
{...startProps}
inputProps={{
...startProps.inputProps,
disabled: true,
}}
/>
Optional: Add the disablePast property to the <MobileDateRangePicker /> component

Proper way to use react-hook-form Controller with Material-UI Autocomplete

I am trying to use a custom Material-UI Autocomplete component and connect it to react-hook-form.
TLDR: Need to use MUI Autocomplete with react-hook-form Controller without defaultValue
My custom Autocomplete component takes an object with the structure {_id:'', name: ''} it displays the name and returns the _id when an option is selected. The Autocomplete works just fine.
<Autocomplete
options={options}
getOptionLabel={option => option.name}
getOptionSelected={(option, value) => option._id === value._id}
onChange={(event, newValue, reason) => {
handler(name, reason === 'clear' ? null : newValue._id);
}}
renderInput={params => <TextField {...params} {...inputProps} />}
/>
In order to make it work with react-hook-form I've set the setValues to be the handler for onChange in the Autocomplete and manually register the component in an useEffect as follows
useEffect(() => {
register({ name: "country1" });
},[]);
This works fine but I would like to not have the useEffect hook and just make use of the register somehow directly.
Next I tried to use the Controller component from react-hook-form to proper register the field in the form and not to use the useEffect hook
<Controller
name="country2"
as={
<Autocomplete
options={options}
getOptionLabel={option => option.name}
getOptionSelected={(option, value) => option._id === value._id}
onChange={(event, newValue, reason) =>
reason === "clear" ? null : newValue._id
}
renderInput={params => (
<TextField {...params} label="Country" />
)}
/>
}
control={control}
/>
I've changed the onChange in the Autocomplete component to return the value directly but it doesn't seem to work.
Using inputRef={register} on the <TextField/> would not cut it for me because I want to save the _id and not the name
HERE is a working sandbox with the two cases. The first with useEffect and setValue in the Autocomplete that works. The second my attempt in using Controller component
Any help is appreciated.
LE
After the comment from Bill with the working sandbox of MUI Autocomplete, I Managed to get a functional result
<Controller
name="country"
as={
<Autocomplete
options={options}
getOptionLabel={option => option.name}
getOptionSelected={(option, value) => option._id === value._id}
renderInput={params => <TextField {...params} label="Country" />}
/>
}
onChange={([, { _id }]) => _id}
control={control}
/>
The only problem is that I get an MUI Error in the console
Material-UI: A component is changing the uncontrolled value state of Autocomplete to be controlled.
I've tried to set an defaultValue for it but it still behaves like that. Also I would not want to set a default value from the options array due to the fact that these fields in the form are not required.
The updated sandbox HERE
Any help is still very much appreciated
The accepted answer (probably) works for the bugged version of Autocomplete. I think the bug was fixed some time after that, so that the solution can be slightly simplified.
This is very useful reference/codesandbox when working with react-hook-form and material-ui: https://codesandbox.io/s/react-hook-form-controller-601-j2df5?
From the above link, I modified the Autocomplete example:
import TextField from '#material-ui/core/TextField';
import Autocomplete from '#material-ui/lab/Autocomplete';
const ControlledAutocomplete = ({ options = [], renderInput, getOptionLabel, onChange: ignored, control, defaultValue, name, renderOption }) => {
return (
<Controller
render={({ onChange, ...props }) => (
<Autocomplete
options={options}
getOptionLabel={getOptionLabel}
renderOption={renderOption}
renderInput={renderInput}
onChange={(e, data) => onChange(data)}
{...props}
/>
)}
onChange={([, data]) => data}
defaultValue={defaultValue}
name={name}
control={control}
/>
);
}
With the usage:
<ControlledAutocomplete
control={control}
name="inputName"
options={[{ name: 'test' }]}
getOptionLabel={(option) => `Option: ${option.name}`}
renderInput={(params) => <TextField {...params} label="My label" margin="normal" />}
defaultValue={null}
/>
control is from the return value of useForm(}
Note that I'm passing null as defaultValue as in my case this input is not required. If you'll leave defaultValue you might get some errors from material-ui library.
UPDATE:
Per Steve question in the comments, this is how I'm rendering the input, so that it checks for errors:
renderInput={(params) => (
<TextField
{...params}
label="Field Label"
margin="normal"
error={errors[fieldName]}
/>
)}
Where errors is an object from react-hook-form's formMethods:
const { control, watch, errors, handleSubmit } = formMethods
So, I fixed this. But it revealed what I believe to be an error in Autocomplete.
First... specifically to your issue, you can eliminate the MUI Error by adding a defaultValue to the <Controller>. But that was only the beginning of another round or problems.
The problem is that functions for getOptionLabel, getOptionSelected, and onChange are sometimes passed the value (i.e. the _id in this case) and sometimes passed the option structure - as you would expect.
Here's the code I finally came up with:
import React from "react";
import { useForm, Controller } from "react-hook-form";
import { TextField } from "#material-ui/core";
import { Autocomplete } from "#material-ui/lab";
import { Button } from "#material-ui/core";
export default function FormTwo({ options }) {
const { register, handleSubmit, control } = useForm();
const getOpObj = option => {
if (!option._id) option = options.find(op => op._id === option);
return option;
};
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<Controller
name="country"
as={
<Autocomplete
options={options}
getOptionLabel={option => getOpObj(option).name}
getOptionSelected={(option, value) => {
return option._id === getOpObj(value)._id;
}}
renderInput={params => <TextField {...params} label="Country" />}
/>
}
onChange={([, obj]) => getOpObj(obj)._id}
control={control}
defaultValue={options[0]}
/>
<Button type="submit">Submit</Button>
</form>
);
}
import { Button } from "#material-ui/core";
import Autocomplete from "#material-ui/core/Autocomplete";
import { red } from "#material-ui/core/colors";
import Container from "#material-ui/core/Container";
import CssBaseline from "#material-ui/core/CssBaseline";
import { makeStyles } from "#material-ui/core/styles";
import TextField from "#material-ui/core/TextField";
import AdapterDateFns from "#material-ui/lab/AdapterDateFns";
import LocalizationProvider from "#material-ui/lab/LocalizationProvider";
import React, { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
export default function App() {
const [itemList, setItemList] = useState([]);
// const classes = useStyles();
const {
control,
handleSubmit,
setValue,
formState: { errors }
} = useForm({
mode: "onChange",
defaultValues: { item: null }
});
const onSubmit = (formInputs) => {
console.log("formInputs", formInputs);
};
useEffect(() => {
setItemList([
{ id: 1, name: "item1" },
{ id: 2, name: "item2" }
]);
setValue("item", { id: 3, name: "item3" });
}, [setValue]);
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Container component="main" maxWidth="xs">
<CssBaseline />
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<Controller
control={control}
name="item"
rules={{ required: true }}
render={({ field: { onChange, value } }) => (
<Autocomplete
onChange={(event, item) => {
onChange(item);
}}
value={value}
options={itemList}
getOptionLabel={(item) => (item.name ? item.name : "")}
getOptionSelected={(option, value) =>
value === undefined || value === "" || option.id === value.id
}
renderInput={(params) => (
<TextField
{...params}
label="items"
margin="normal"
variant="outlined"
error={!!errors.item}
helperText={errors.item && "item required"}
required
/>
)}
/>
)}
/>
<button
onClick={() => {
setValue("item", { id: 1, name: "item1" });
}}
>
setValue
</button>
<Button
type="submit"
fullWidth
size="large"
variant="contained"
color="primary"
// className={classes.submit}
>
submit
</Button>
</form>
</Container>
</LocalizationProvider>
);
}
I do not know why the above answers did not work for me, here is the simplest code that worked for me, I used render function of Controller with onChange to change the value according to the selected one.
<Controller
control={control}
name="type"
rules={{
required: 'Veuillez choisir une réponse',
}}
render={({ field: { onChange, value } }) => (
<Autocomplete
freeSolo
options={['field', 'select', 'multiple', 'date']}
onChange={(event, values) => onChange(values)}
value={value}
renderInput={(params) => (
<TextField
{...params}
label="type"
variant="outlined"
onChange={onChange}
/>
)}
/>
)}
Thanks to all the other answers, as of April 15 2022, I was able to figure out how to get this working and render the label in the TextField component:
const ControlledAutocomplete = ({
options,
name,
control,
defaultValue,
error,
rules,
helperText,
}) => (
<Controller
name={name}
control={control}
defaultValue={defaultValue}
rules={rules}
render={({ field }) => (
<Autocomplete
disablePortal
options={options}
getOptionLabel={(option) =>
option?.label ??
options.find(({ code }) => code === option)?.label ??
''
}
{...field}
renderInput={(params) => (
<TextField
{...params}
error={Boolean(error)}
helperText={helperText}
/>
)}
onChange={(_event, data) => field.onChange(data?.code ?? '')}
/>
)}
/>
);
ControlledAutocomplete.propTypes = {
options: PropTypes.arrayOf({
label: PropTypes.string,
code: PropTypes.string,
}),
name: PropTypes.string,
control: PropTypes.func,
defaultValue: PropTypes.string,
error: PropTypes.object,
rules: PropTypes.object,
helperText: PropTypes.string,
};
In my case, options is an array of {code: 'US', label: 'United States'} objects. The biggest difference is the getOptionLabel, which I guess needs to account for if both when you have the list open (and option is an object) and when the option is rendered in the TextField (when option is a string) as well as when nothing is selected.
I have made it work pretty well including multiple tags selector as follow bellow. It will work fine with mui5 and react-hook-form 7
import { useForm, Controller } from 'react-hook-form';
import Autocomplete from '#mui/material/Autocomplete';
//setup your form and control
<Controller
control={control}
name="yourFiledSubmitName"
rules={{
required: 'required field',
}}
render={({ field: { onChange } }) => (
<Autocomplete
multiple
options={yourDataArray}
getOptionLabel={(option) => option.label}
onChange={(event, item) => {
onChange(item);
}}
renderInput={(params) => (
<TextField {...params} label="Your label" placeholder="Your placeholder"
/>
)}
)}
/>
Instead of using controller, with the help of register, setValue of useForm and value, onChange of Autocomplete we can achieve the same result.
const [selectedCaste, setSelectedCaste] = useState([]);
const {register, errors, setValue} = useForm();
useEffect(() => {
register("caste");
}, [register]);
return (
<Autocomplete
multiple
options={casteList}
disableCloseOnSelect
value={selectedCaste}
onChange={(_, values) => {
setSelectedCaste([...values]);
setValue("caste", [...values]);
}}
getOptionLabel={(option) => option}
renderOption={(option, { selected }) => (
<React.Fragment>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option}
</React.Fragment>
)}
style={{ width: "100%" }}
renderInput={(params) => (
<TextField
{...params}
id="caste"
error={!!errors.caste}
helperText={errors.caste?.message}
variant="outlined"
label="Select caste"
placeholder="Caste"
/>
)}
/>
);

Resources