How do I get the date value from react-datepicker to register of the react-hook-form - reactjs

I'm making a registration form with required birthday field using react-datepicker and react-hook-form.
After clicking onSubmit button, all data are logged in the console except birthDate (which is null). How do I get the date value from react-datepicker to register of the react-hook-form?
RegistrationForm interface
export interface RegistrationForm {
username: string;
birthDate: Date;
email: string;
password: string;
passwordConfirm: string;
}
const {register, handleSubmit, formState: {errors}, watch, control } = useForm<RegistrationForm>()
const onSubmit = async (data: RegistrationForm) => {
console.log(data)
}
const [selectedBirthDate, setSelectedBirthDate] = useState<Date>(new Date())
const handleDateChange = (date: Date | null) => {
if(date != null) {
setSelectedBirthDate(date)
}
}
In my Controller tag
<Controller
name={"birthDate"}
control={control}
defaultValue={selectedBirthDate}
render={({field}) => {
return(
<DatePicker
onChange={(date: Date) => handleDateChange(date)}
selected={selectedBirthDate}
placeholderText="Enter your birth date"
/>
)
}}
/>
Username form
<input
type="text"
placeholder="username"
{...register("username", {
required: true,
maxLength: 20,
minLength: 5
})}
/>

You forgot to link your <Controller /> with the <DatePicker /> component. So it is important to use at least value and onChange and pass it to the <DatePicker /> props.
<Controller
name={"birthDate"}
control={control}
defaultValue={new Date()}
render={({ field: { onChange, value } }) => {
return (
<DatePicker
onChange={onChange}
selected={value}
placeholderText="Enter your birth date"
/>
);
}}
/>
If you use RHF you also don't need useState to handle your state changes as RHF will handle those changes for you.
UPDATE
To display an error, you need the errors object, which RHF provides via the formState property. But you need to create a validation rule first: you can choose between rules provided by RHF and custom rules, see here for more info (please don't get confused, you can find the rules in the docs under register, because register and <Controller /> have the same interface for the validation rules).
You can then add the rules using the rules prop from <Controller />. As an example, I have added a validation that checks if the selected date is older than 2 years. For displaying the error i used the <ErrorMessage /> component provided by the package #hookform/error-message, but you could also just check if errors.birthDate exists and show your own custom error message.
const sub2Years = subYears(2);
const isOlderThan2Years = (date) =>
isBefore(sub2Years(new Date()), date) || "Date is not older than 2 years";
<Controller
name={"birthDate"}
control={control}
defaultValue={new Date()}
rules={{ validate: { isOlderThan2Years } }}
render={({ field: { onChange, value } }) => {
return (
<DatePicker
onChange={onChange}
selected={value}
placeholderText="Enter your birth date"
/>
);
}}
/>
<ErrorMessage
errors={errors}
name="birthDate"
render={({ message }) => <p>{message}</p>}
/>

Related

How can I set validation rules when all fields are empty React Hook Form

I'm using React Hook Form V7. I have two input fileds and using Controller to control the input.
The below one is what I did now, each field is required.
<form onSubmit={handleSubmit((data) => console.log(data))}>
<Controller
render={({ field }) => <TextField value={field.value} onChange={field.onChange} />}
name="test1"
control={control}
rules={{ required: true }}
/>
<Controller
render={({ field }) => <TextField value={field.value} onChange={field.onChange} />}
name="test2"
control={control}
rules={{ required: true }}
/>
<input type="submit" />
</form>
My question is how can I set instead of each one is required, I want the error message showing when both fields are empty, if only one is empty is accepted.
Is there any onSubmit validation on React Hook Form? Or I need to do the normal validation on the onSubmit function to check the value then set if error message show?
Edit:
this is what I did now:
const [submitError, setSubmitError] = useState(false)
onSubmit((data) => {
const { test1, test2 } = data
if (!test1 && !test2) {
setSubmitError(true)
} else {
setSubmitError(false)
// do submit action
}
})
const errorEmptyMessage = "one of test1 and test2 should has value"
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
render={({ field }) => <TextField value={field.value} onChange={field.onChange} />}
name="test1"
control={control}
rules={{ required: true }}
/>
<Controller
render={({ field }) => <TextField value={field.value} onChange={field.onChange} />}
name="test2"
control={control}
rules={{ required: true }}
/>
{submitError && emptyMessage}
<input type="submit" />
</form>
)
I wonder if React Hook Form has a built-in function to do this?
Is there any onSubmit validation on React Hook Form? Or I need to do
the normal validation on the onSubmit function to check the value then
set if error message show?
Yes, You have a nice solution which I recommend it to use nested of normal validation, its name schema validation like YUP, Simply what you need to do is add needed rule, for example (from react-hook-form):
import React from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from '#hookform/resolvers/yup';
import * as yup from "yup";
const schema = yup.object({
firstName: yup.string().required(),
age: yup.number().positive().integer().required(),
}).required();
export default function App() {
const { register, handleSubmit, formState:{ errors } } = useForm({
resolver: yupResolver(schema)
});
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName")} />
<p>{errors.firstName?.message}</p>
<input {...register("age")} />
<p>{errors.age?.message}</p>
<input type="submit" />
</form>
);
}
If you read the above code, you see you are build a schema for each field needed, and you have a lot of options, for example in your case you may use when to handling on x and y is empty and so on..., also you have a lot of validation schema build as an object like int, min and required, you can check this part.
Also you can do that via onSubmit on normal flow, like this:
const onSubmit = () => {
throw new Error('Something is wrong')
}
handleSubmit(onSubmit).catch((e) => {
// you will need to catch that error
})
and the idea here you check what you need and you can throw the error, exampe:
const { register, handleSubmit } = useForm();
const onSubmit = (data, e) => console.log(data, e);
const onError = (errors, e) => console.log(errors, e);
return (
<form onSubmit={handleSubmit(onSubmit, onError)}>
<input {...register("firstName")} />
<input {...register("lastName")} />
<button type="submit">Submit</button>
</form>
);
But from my side, I suggest to use schema validation, its really useful base on my experience with react hook form.
Update 1: More Example:
In above is example how you can build conditions to resolve issue, but simply visit yup and check when,
const schema =
object().shape({
a: string().when(["a", "b"], {
is: (a, b) => !a && !b
then: string().required("At least one is to be selected"),
otherwise: string() // unnecessary
}),
a: string().when(["a", "b"], {
is: (a, b) => !a && !b
then: string().required("At least one is to be selected"),
otherwise: string() // unnecessary
})
});

react-hook-form not triggering onSubmit when using Controller

I have the following form:
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="voucherPrice"
control={control}
defaultValue={false}
rules={{ required: true }}
render={({ field: { onChange, value, ref } }) => (
<Input {...field} onChange={(ev) => handlePriceInputChange(ev)} value={price} type="number" innerRef={ref} />
)}
/>
<p>{errors.voucherPrice?.message}</p>
<Button
variant="contained"
sx={{ mt: 1, mr: 1 }}
type="submit"
>
{"Continue"}
</Button>
</form>
and with this configuration:
function PriceSelection(props) {
const {
register,
handleSubmit,
control,
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});
const onSubmit = (data) => {
console.log("does not work?", data);
};
const classes = useStylesPriceSelection();
const [selected, setSelected] = useState(false);
const [price, setPrice] = useState("");
const handlePriceInputChange = (ev) => {
console.log("change", price);
setPrice(parseInt(ev.target.value));
};
The function onSubmit does not trigger when I press the submit button. Also I would like the input field to be filled by default by the state price and its value to be sent with the parameter data on the function onSubmit when I push the submit button.
You are mixing useState with react-hook-form and are not updating react-hook-form's internal form state. You don't need to declare a useState for your field.
In your example you are destructering onChange from the field object of <Controller /> but you are never using it for your <Input /> component. Therefore react-hook-form can't update it's form state. As you set your field to be required the onSubmit callback won't get triggered because react-hook-form will never receive an update or value for it.
The correct way would be:
<Controller
name="voucherPrice"
control={control}
rules={{ required: true }}
render={({ field: { ref, onChange, value, ...field } }) => (
<Input {...field} onChange={onChange} value={value} type="number" innerRef={ref} />
)}
/>
Or even shorter:
<Controller
name="voucherPrice"
control={control}
rules={{ required: true }}
render={({ field: { ref, ...field } }) => (
<Input {...field} type="number" innerRef={ref} />
)}
/>
UPDATE
If you need to have access to the value outside of the <Controller />, you should use react-hook-form's watch method. This will allow you to subscribe to the latest value of the voucherPrice field and use it inside your component -> Docs
If you want to set or update the value programmatically you can use the setValue method from react-hook-form -> Docs
const { control, handleSubmit, watch, setValue } = useForm();
const voucherPrice = watch("voucherPrice");
const onButtonClick = () => {
setValue("voucherPrice", <newValue>);
}
If you really need to have a separate useState for your value and want to update it additionally to your react-hook-form field update, you could do the following:
<Controller
name="voucherPrice"
control={control}
rules={{ required: true }}
render={({ field: { ref, onChange, value, ...field } }) => (
<Input
{...field}
onChange={(v) => {
onChange(v);
handlePriceInputChange(v);
}}
value={value}
type="number"
innerRef={ref}
/>
)}
/>
But i would suggest to use the react-hook-form only solution, as it has all the functionality you need to manage your form state.

react-hooks-form dependent fields with Material UI TextField

With react-hooks-form I am trying to update one field based on another field value. The error I am facing is that my value is not registered in the data object.
This is my code:
const { handleSubmit, control } = useForm({});
const [dateValue, setDateValue] = useState()
const onSubmit = (data) => {console.log(data.week)} \\ undefined
<form
className={classes.root}
autoComplete="on"
onSubmit={handleSubmit(onSubmit)}
>
<Controller
name="date"
control={control}
defaultValue={props.operation === 'edit' ? props.values.date : null}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<TextField
id="date"
type="date"
label="date"
value={value}
className={classes.textField}
onChange={(event) => {
onChange(event.target.value);
setDateValue(event.target.value);
}}
error={!!error}
helperText={error ? error.message : null}
InputLabelProps={{
shrink: true,
}}
/>
)}
rules={{ required: 'Date is required' }}
/>
<Controller
name="week"
control={control}
defaultValue=""
render={({ field: { onChange, value }, fieldState: { error } }) => (
<TextField
id="week"
type="text"
className={classes.textField}
label="week"
disabled={true}
value={dateValue}
onChange={onChange}
error={!!error}
helperText={error ? error.message : null}
/>
)
/>
</form>
Finally, my week value is the result of a function which returns a string containing week - year concatenation.
The value is updated in the TextField, but it is not registered in the data object. Any idea what am I missing?
On the onChange handler of the date field you are updating only the state of dateValue and not the state of the form itself.
You have 2 options:
in the onChange handler of the date field call the setValue method you get from useForm
watch the value of the date field const date = watch('date') and have a useEffect to update the value of the week field:
const {watch, setValue} = useForm()
const date = watch('date')
useEffect(() => {
const calculateWeekValue = () => {...//}
setValue('week', calculateWeekValue(date))
}, [date, setValue])

Example of using react-hook-form with a Date/Time Picker and using Material-UI?

Does anyone have an example of using react-hook-form with a Date/Time Picker and using Material-UI? I have been able to implement with a Mui TextField that has a type of "datetime-local", and I can set the Date/Time, but I have been unable to get a timestamp to appear in the field upon entering the form using default values for react-hook-form; that is, the date/time value doesn't appear in the picker. Upon manually setting the date/time value and submitting the form, the value is getting properly bound to the react-hook-form "data" object. Some fragments from my code are below. I have replaced irrelevant code with ellipses (...).
import React from 'react';
import { Controller, useForm } from 'react-hook-form';
import { TextField } from '#material-ui/core';
...
interface IMyFormProps {
name: string;
description: string;
occurrenceTimestamp: Date;
}
...
const MyForm: React.FC<IMyFormProps> = (props: IMyFormProps) => {
...
// set up details for ReactHookForm
const { register, control, formState, handleSubmit, errors } = useForm({
defaultValues: {
name: props.name,
description: props.description,
occurrenceTimestamp: props.occurrenceTimestamp
},
mode: "all"
});
...
return (
<form onSubmit=...
// a text input for Name
<TextField
inputRef={register({ required: true })}
name="name"
label="Name"
...
/>
// a text input for Description
<TextField
inputRef={register({ required: true })}
name="description"
label="Description"
...
/>
// The Date/Time Picker
<Controller
render={(props) =>
<TextField
{...props}
type="datetime-local"
label="Occurrence Date/Time"
/>
}
name="occurrenceTimestamp"
control={control}
>
</Controller>
>
</form>
)
}
Could you please try below code?
import React from 'react';
import { Controller, useForm } from 'react-hook-form';
import { TextField } from '#material-ui/core';
...
interface IMyFormProps {
name: string;
description: string;
occurrenceTimestamp: Date;
}
...
const MyForm: React.FC<IMyFormProps> = (props: IMyFormProps) => {
...
// set up details for ReactHookForm
const { register, control, formState, handleSubmit, errors } = useForm({
defaultValues: {
name: props.name,
description: props.description,
occurrenceTimestamp: props.occurrenceTimestamp
},
mode: "all"
});
...
return (
<form onSubmit=...
// a text input for Name
<TextField
inputRef={register({ required: true })}
name="name"
label="Name"
...
/>
// a text input for Description
<TextField
inputRef={register({ required: true })}
name="description"
label="Description"
...
/>
> // The Date/Time Picker
> <Controller
> render={(props) =>
> <TextField
> {...props}
> type="datetime-local"
> label="Occurrence Date/Time"
> />
> }
> name="occurrenceTimestamp"
> control={control}
> >
> </Controller>
>
>
</form>
)
}
> You can use below methods for getting value
<Col>
<DatePicker
name="datetime"
className={"form-control"}
selected={startDate}
**onChange**={date => setStartDate(date)}
showTimeSelect
timeFormat="HH:mm"
timeIntervals={15}
timeCaption="time"
dateFormat="MM-dd-yyyy h:mm"
/>
</Col>
and setStartDate(date)
Good luck and welcome to StackOverflow

React Hook Form with datepicker Range doesn't pick date

I got the following datepickers.
For the Start Date:
<Controller
as={
<DatePicker
selected={travelRoute?.dateStart || new Date()}
selectsStart
startDate={travelRoute?.dateStart}
endDate={travelRoute?.dateEnd}
inline
/>
}
control={control}
rules={{ required: true }}
valueName="selected"
onChange={date => handleStartDateOnChange(date)}
name="dateStart"
placeholderText="Select date"
defaultValue={null}
/>
For the End Date:
<Controller
as={
<DatePicker
name="dateEnd"
selected={travelRoute?.dateEnd || new Date()}
onChange={date => handleEndDateOnChange(date)}
selectsEnd
startDate={travelRoute?.dateStart}
endDate={travelRoute?.dateEnd}
minDate={travelRoute?.dateStart}
inline
/>
}
control={control}
rules={{ required: true }}
valueName="selected"
onChange={date => handleEndDateOnChange(date)}
name="dateEnd"
placeholderText="Select date"
defaultValue={null}
/>
Do I need to add the datepicker props to the datepicker component or to the Controller Component?
The start Datepicker doesn't select the date and the end Datepicker doesn't change the startdate and isn't displaying the range.
I'm saving the data into the travelRoute state with setTravelRoute which is happening in the handleOnChange functions.
EDIT:
Added onChange handler:
const handleStartDateOnChange = date => {
setTravelRoute(prevState => ({
...prevState,
dateStart: date
}))
};
const handleEndDateOnChange = date => {
setTravelRoute(prevState => ({
...prevState,
dateEnd: date
}))
};
Ciao, you are using the wrong approach to customize your onChange event in DatePicker. And onChange event on Controller can't work neihter. Because in this case, you don't have to use as= syntax, but render= syntax like:
<Controller
render={({ onChange }) => (
<DatePicker
...
onChange={date => handleStartDateOnChange(date)}
/>
)}
...
/>
So your code becomes:
Start Date
<Controller
render={({ onChange }) => (
<DatePicker
selected={travelRoute?.dateStart || new Date()}
selectsStart
startDate={travelRoute?.dateStart}
endDate={travelRoute?.dateEnd}
inline
onChange={date => handleStartDateOnChange(date)}
/>
)}
control={control}
rules={{ required: true }}
valueName="selected"
name="dateStart"
placeholderText="Select date"
defaultValue={null}
/>
End Date
<Controller
render={({ onChange }) => (
<DatePicker
name="dateEnd"
selected={travelRoute?.dateEnd || new Date()}
onChange={date => handleEndDateOnChange(date)}
selectsEnd
startDate={travelRoute?.dateStart}
endDate={travelRoute?.dateEnd}
minDate={travelRoute?.dateStart}
inline
/>
)}
control={control}
rules={{ required: true }}
valueName="selected"
name="dateEnd"
placeholderText="Select date"
defaultValue={null}
/>
Here a working example.
Note: I don't know why DatePicker in codesandbox looks so ugly. Could be the Controller because in this other codesandbox looks good.
I have had a different approach to the solution of this issue and wanted to share it.
I answered some related issue in the following post: https://stackoverflow.com/a/72585781/19320134
Anyway, one way to make the code more reusable is to put these controllers in separate files, so that only the function name is imported when calling them.
export const DateRange = ({ name, control, label }) => {
const [dateRange, setDateRange] = useState([null, null]);
const [startDate, endDate] = dateRange;
return (
<Controller
//is a prop that we get back from the useForm Hook and pass into the input.
control={control}
//is how React Hook Form tracks the value of an input internally.
name={name}
//render is the most important prop; we pass a render function here.
render={({
//The function has three keys: field , fieldState, and formState.
field, // The field object exports two things (among others): value and onChange
}) => (
<>
<DatePicker
selectsRange={true}
startDate={startDate}
endDate={endDate}
onChange={(e) => {
setDateRange(e);
field.onChange(e);
}}
isClearable={true}
className="form-control"
/>
</>
)}
rules={{
required: `The ${label} field is required`,
}}
/>
And you can call it in the necesary file like this
import { useForm, Controller, FormProvider } from "react-hook-form";
import {DateRange} from "./your file !"
const defaultValues = {
dateRange: [],
};
export default function ComponentUsingDateRange() {
const methods = useForm({
defaultValues,
mode: "onChange",
});
const { register, handleSubmit, control, setValue } = methods;
return(
<>
<FormProvider {...methods}>
<DateRange
control={control}
name="dateRange"
label="Range of dates"
/>
</FormProvider>
</>
);
}

Resources