Material UI (MUI) date picker with react-hook-form - javascript

I'm creating a form which has a date field. I'm using MUI and react-hook-form for validation. I've tried to render the field in two different ways, but when submitting my form I'm not getting the expected value:
Render 1
Using a Controller component:
const [originalReleaseDate, setOriginalReleaseDate] = useState(null);
<Controller
name={"original_release_date"}
defaultValue={originalReleaseDate}
control={control}
render={({field}) =>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
label="Original Release Date"
value={originalReleaseDate}
onChange={(newValue) => {
setOriginalReleaseDate(newValue);
}}
renderInput={(params) =>
<TextField
{...params}
/>}
/>
</LocalizationProvider>
}
/>
when I render the field this way, I'm getting null for original_release_date after submitting the form.
Render 2
Registering the field directly using {...register("reissue_release_date")} instead of react-hook-form Controlled component.
const [reissueReleaseDate, setReissueReleaseDate] = useState(null);
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
label="Reissue Release Date"
value={reissueReleaseDate}
onChange={(newValue) => {
setReissueReleaseDate(newValue);
}}
renderInput={(params) =>
<TextField
{...params}
{...register("reissue_release_date")}
/>}
/>
</LocalizationProvider>
this way is working half way. If I manually type the date then I'm getting its value on submit, BUT if I use the date picker and then submitting the form I get "".
Any idea what's going on?

Just modified the above answer with a bracket.
const [reqDate, setreqDate] = useState(new Date());
<Controller
name="reqDate"
defaultValue={reqDate}
control={control}
render={
({ field: { onChange, ...restField } }) =>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
label="Request Date"
onChange={(event) => { onChange(event); setreqDate(event); }}
renderInput={(params) =>
<TextField
{...params}
/>}
{...restField}
/>
</LocalizationProvider>
}
/>

InputDate.propTypes = {
name: PropTypes.string,
label: PropTypes.string,
};
export default function InputDate({ name, label }) {
const { control } = useFormContext();
return (
<Controller
name={name}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<LocalizationProvider dateAdapter={AdapterMoment} >
<DesktopDatePicker
label={label}
control={control}
inputFormat="DD-MM-YYYY"
value={value}
onChange={(event) => { onChange(event); }}
renderInput={(params) => <TextField {...params} error={!!error} helperText={error?.message} />}
/>
</LocalizationProvider>
)} />
)
}

In most cases, if you are using react-hook-form, you don't need to track form fields with useState hook.
Using a Controller component is the right way to go. But there is a problem with onChange handler in your 1st method.
When you submit form, you are getting default date null because field is destructed, but it's not passed to DatePicker. So, onChange prop of field is not triggered when date is changed and react-hook-form doesn't have new date.
Here's how your render method should be
render={({field}) =>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
label="Original Release Date"
renderInput={(params) =>
<TextField
{...params}
/>}
{...field}
/>
</LocalizationProvider>
}
If for some reason, you need to update component state then you have to send data to react-hook-form and then update local state
render={({field: {onChange,...restField}) =>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
label="Original Release Date"
onChange={(event) => {
onChange(event);
setOriginalReleaseDate(event.target.value);
}}
renderInput={(params) =>
<TextField
{...params}
/>}
{...restField}
/>
</LocalizationProvider>
}

I couldn't replicate your setup, but my guess is that in the first render the
reference to the 'setOriginalReleaseDate' is lost when being passed through the Controller's render arrow function.
...
onChange={(newValue) => {
setOriginalReleaseDate(newValue);
}}
...
so, try putting the logic in a defined function like:
const handleOriginalReleaseDateChange = (newValue) => {
setOriginalReleaseDate(newValue);
};
and change the onChange to call the function.
...
onChange={handleOriginalReleaseDateChange}
...

Related

React: MUI autocomplete with MUI form

I've been using a MUI form like this:
<Box component="form" onSubmit={event => {
return handleSubmit(event);
}} noValidate sx={{mt: 1}}>
<TextField
margin="normal"
required
fullWidth
id="title"
label="Titel"
name="title"
autoFocus
/>
<TextField
margin="normal"
required
multiline
rows={10}
fullWidth
label="Inhalt"
name="content"
id="content"
autoComplete="off"
/>
</Box>
This allowed me to extract the values from the MUI TextField components by using FormData:
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const data = new FormData(event.currentTarget);
let newsResponse = await createNews({
title: data.get('title'),
content: data.get('content'),
});
}
This works fine. Now I wanted to add a MUI Autocomplete component to the form:
<Autocomplete
multiple
id="tags"
className={props.className}
open={open}
isOptionEqualToValue={(option, value) => option.name === value.name}
getOptionLabel={(option) => option.name}
options={tags}
renderInput={(params) => (
<TextField
{...params}
label="Tags"
required
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color="inherit" size={20}/> : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
However, I found no way to access the value of said Autocomplete component. It does not even have a name attribute and adding a name attribute to the inner TextField component does not work either.
How can I access the value of it in manner like data.get('tags')?
Considering that both are MUI components I would expect them to have the same API.
The useState hook, something like this:
function MyForm() {
const [values, setValues] = useState('');
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
console.log(values);
};
return (
<form onSubmit={handleSubmit}>
<Autocomplete
multiple
id="tags"
className={props.className}
open={open}
isOptionEqualToValue={(option, value) => option.name === value.name}
getOptionLabel={(option) => option.name}
options={tags}
onChange={(event: any, newValues: string[] | null) => {
setValues(newValues || '');
}}
renderInput={(params) => (
<TextField
{...params}
label="Tags"
required
InputProps={{
...params.InputProps,
endAdornment: (
<>
{loading ? <CircularProgress color="inherit" size={20}/> : null}
{params.InputProps.endAdornment}
</>
),
}}
/>
)}
/>
<button type="submit">Submit</button>
</form>
);
}

How to lose focus from Mui DesktopDatePicker field when clicking on another field

Here is the codesandbox: https://codesandbox.io/s/frosty-cherry-4cp1sx?file=/src/App.js
So I have two DesktopDatePickers from Material UI and I want that when I click anywhere on the field, it opens the Date Picker. However, when I click on one of the fields then click on the other one, the previous one is focused.
I tried using
<DesktopDatePicker
label="Created Date From"
inputFormat="MM/dd/yyyy"
value={dateCreated}
open={openCreatedFrom}
inputRef={createdFromRef}
on
clearable={true}
onClose={() => {
setOpenCreatedFrom(false);
}}
onChange={(newValue) => {
setDateCreated(newValue);
}}
renderInput={(params) => (
<TextField
{...params}
fullWidth
onClick={() => {
setOpenCreatedFrom(!openCreatedFrom);
if (createdToRef && createdToRef.current !== null)
createdToRef.current.blur();
}}
size="small"
error={false}
/>
)}
/>
and
<DesktopDatePicker
label="Created Date From"
inputFormat="MM/dd/yyyy"
value={dateCreated}
open={openCreatedFrom}
inputRef={createdFromRef}
on
clearable={true}
onClose={() => {
setOpenCreatedFrom(false);
}}
onChange={(newValue) => {
setDateCreated(newValue);
}}
renderInput={(params) => (
<TextField
{...params}
fullWidth
onClick={() => {
setOpenCreatedFrom(!openCreatedFrom);
if (createdFromRef && createdFromRef.current !== null)
createdFromRef.current.focus();
}}
size="small"
error={false}
/>
)}
/>
as well as calling the focus() and blur() functions in the '''onClose()``` function but none of them worked despite createdFromRef and createdToRef both referencing the fields.

Geeting value differance in MUI date picker

I am using Mui 5 date picker my issue is if I change the date using calendar I will get the expected result like the example I selected 26 I get this
"2022-01-26T09:16:10.000Z"
but when I edit directly in the field example I selected 27 I get this
"2022-01-26T18:30:00.000Z" because of this I will get a validation error I am not understanding why this happening and after the edit, if the select date from again calendar 26 then the final value is
"2022-01-25T18:30:00.000Z"
for external I am using momentjs and fromik.
const [value, setValue] = React.useState(null);
return (
<Box>
{JSON.stringify(value)}
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
label="Basic example"
value={value}
onChange={(newValue) => {
setValue(newValue);
}}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
</Box>
);
Using moment, set the initial value to start of the day startOf.
const [value, setValue] = React.useState(moment().startOf('day'))
Code snippet:
export default function BasicDatePicker() {
const [value, setValue] = React.useState(moment().startOf('day'));
return (
<LocalizationProvider dateAdapter={AdapterMoment} >
<DatePicker
label="Basic example"
value={value}
onChange={(newValue) => {
setValue(moment(newValue));
}}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
);
}
Sandbox : here
EDIT:
if you have a specific date to be passed,
React.useState(moment('03-03-2021').startOf("day"))

Material-ui Autocomplete: Add a value to startAdornment

I have autocomplete with multiple selection permission.
https://codesandbox.io/s/bold-jackson-dkjmb?file=/src/App.js
In the example I have 3 options for cities. How can I manually add automatic added value in TextField when something is selected?
In other words here:
renderInput={(params) => {
console.log(params);
return (
<TextField
{...params}
variant="outlined"
label="Cities"
placeholder="Enter cities"
autoComplete="off"
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{params.InputProps.endAdornment}
</React.Fragment>
)
}}
/>
);
}}
I want to be able to add to params.InputProps.startAdornment a value before rendering the textfield.
as every selected object seems to be very complex object, how I can do this manually(It is too complicated to push())? Any ideas how I can add object like this:
manually?
the value of startAdornment is undefined until a value is chosen from the dropdown/checkbox. So, you could add startAdornment property to the InputProps like below,
import { Chip } from '#material-ui/core';
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
chip: {
margin: theme.spacing(0.5, 0.25)
}
}));
const classes = useStyles();
const handleDelete = (item) => () => {...};
renderInput={(params) => {
console.log(params);
return (
<TextField
{...params}
variant="outlined"
label="Cities"
placeholder="Enter cities"
autoComplete="off"
InputProps={{
...params.InputProps,
startAdornment: (
<Chip
key={"manual"}
tabIndex={-1}
label={"manually added"}
className={classes.chip}
onDelete={handleDelete("blah")}
deleteIcon // requires onDelete function to work
/>
),
endAdornment: (
<React.Fragment>
{params.InputProps.endAdornment}
</React.Fragment>
)
}}
/>
);
}}
The other solution didn't work 100% from myside,
As it adds the automatic field,
But new selected options -> are selected but chips not showing next to the automatic added option!!
So to fix this problem I made a few changes:
<TextField
{...params}
variant="outlined"
label="Cities"
placeholder="Enter cities"
autoComplete="off"
InputProps={{
...params.InputProps,
startAdornment: (
<>
<Chip
key={"manual"}
tabIndex={-1}
label={"manually added"}
className={classes.chip}
onDelete={handleDelete("blah")}
deleteIcon // requires onDelete function to work
/>
<React.Fragment> //the change
{" "}
{params.InputProps.startAdornment}{" "}. //the change
</React.Fragment>
</>
),
}}
endAdornment: (
<React.Fragment>
{params.InputProps.endAdornment}
</React.Fragment>
)
}}
/>

How to pass the value of input to component state on onChange method using formik

I have a helper for formik using it I manage my state on the form my problem is how can I change component state on onChange method I tried something like below but it doesnt work out !!! anyone can help me.. Whenever user trigger onChange method I want to take the value of it and request to api by this value and add new select on the form... Thank you so much
** custom formik lib
const MaterialUISelectField = ({
errorString,
label,
children,
value,
name,
onChange,
onBlur,
required,
}) => {
return (
<FormControl fullWidth>
<InputLabel required={required}>{label}</InputLabel>
<Select
name={name}
onChange={onChange}
onBlur={onBlur}
value={value}
>
{children}
</Select>
<FormHelperText error={true}> {errorString}</FormHelperText>
</FormControl>
);
};
const FormikSelect = ({ name, items, label, required = false, error }) => {
return (
<div className="FormikSelect">
<Field
name={name}
as={MaterialUISelectField}
label={label}
errorString={error ? error : <ErrorMessage name={name} />}
required={required}
>
<MenuItem disabled value="">
<em>{label} Seçiniz</em>
</MenuItem>
{items != null && items.map(item => (
<MenuItem key={item.Id} value={item.Id}>
{item.Ad}
</MenuItem>
))}
</Field>
</div>
);
};
** component where I use the custom formik lib
const [state, setState] = React.useState('');
<FormikSelect
name="aa"
label="aa"
items={data}
onChange={(e) => setState(e.target.value)} />

Categories

Resources