react-hooks-form dependent fields with Material UI TextField - reactjs

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])

Related

How to correctly make a get request during onChange

I want to make a get request 2 seconds after the key is pressed on onChange. I'm using useForm and I know it can be done via lodash.throttle but it's not necessary for me. Help me write a function please
this is my get request
const response = await searchContractorsById(data.INN);
<Controller
name="inn"
control={control}
render={({ field: { value, onChange } }) => (
<TextField
value={value}
onChange={onChange}
type="number"
fullWidth
size="small"
placeholder="Например, 6163152794"
error={!!errors?.inn}
helperText={errors?.inn && errors.inn?.message}
/>
)}
/>
In case you wish to do this request at every onChange, you can do something like this:
const handleChange = (event, onChange) => {
setTimeout(() => {
searchContractorsById(data.INN)
.then(() => {...use results here})
.catch(e => ...)
}, 2000)
onChange(event)
}
<Controller
name="inn"
control={control}
render={({ field: { value, onChange } }) => (
<TextField
value={value}
onChange={(e) => handleChange(e, onChange)}
type="number"
fullWidth
size="small"
placeholder="Например, 6163152794"
error={!!errors?.inn}
helperText={errors?.inn && errors.inn?.message}
/>
)}
/>

mui autocomplete with react-hook-form: defaultValue in formData

I'm setting the defaultValue of the Autocopmlete component as below:
<Controller
control={control}
name={name}
render={({field: {onChange, value}}) => (
<Autocomplete
freeSolo={freeSolo}
options={options}
renderInput={params => {
return <TextField {...params} label={label} margin="normal" variant="outlined" onChange={onChange} />
}}
onChange={(event, values, reason) => onChange(values)}
defaultValue={defaultValue}
/>
)}
/>
the value is well displayed based on the defaultValue.
However when I click on the submit button, the value of the Autocomplete field is always undefined if I don't use the autocomplete component.
Here are how I register the hook and component (simplified code)
const customerSchema = yup.object().shape({
route: yup.string().nullable()
})
const {control, handleSubmit} = useForm({
resolver: yupResolver(customerSchema)
})
const onSubmit = formData => {
console.log(formData) // autocomplete is undefined if no action on the autocomplete
}
<form noValidate autoComplete="off" onSubmit={handleSubmit(onSubmit)}>
<AutoCompleteInput
control={control}
name="route"
label="route"
options={customers_routes.map(option => option.route_name)}
defaultValue={customer.route}
freeSolo={false}
/>
<Button type="submit">
Update
</Button>
</form>
What should I do to have the route field always available in the form data when I click on submit?
Why don't you use defaultValues option with useForm:
const {control, handleSubmit} = useForm({
resolver: yupResolver(customerSchema),
defaultValues: { route: customer.route },
});
and instead of sending defaultValue prop to AutoComplete you can use value prop, like this:
<Controller
control={control}
name={name}
render={({ field: { onChange, value } }) => (
<Autocomplete
freeSolo={freeSolo}
options={options}
renderInput={(params) => {
return (
<TextField
{...params}
label={label}
margin="normal"
variant="outlined"
onChange={onChange}
/>
);
}}
onChange={(event, values, reason) => onChange(values)}
value={value}
/>
)}
/>
Here is a simplest way to use an Autocomplete with react hook from , render your Autocomplete component inside Controller from react hook from, use onChange and value from the render function to control the value
<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}
/>
)}
/>
)}

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.

In the onSubmit event, how to pass the TextField Value without any value passed by the form React JS

I am new to react js and react hook.
I have three text fields in my form.
Ex: First Text Field value=5
Second Text Field value=3
Third Text Field value=FirstTextField-SecondTextField.
The first Text field value is the default value. But the second text field is editable. When I change the text field value. It's automatically updated to (based on calculation) text field third.
const formvalues = useForm({
mode: 'onSubmit',
reValidateMode: 'onChange',
defaultValues: {
workhrs:0,
remainhrs:0,
actualhrs:5
},
resolver: yupResolver(schema),
});
const { control, watch, reset, handleSubmit, onChange, formState, getValues } = methods;
const [workhrsvalue, setWorkhrsValue]=useState();
const [remainhrsvalue, setRemainhrsValue]=useState();
const handleTextChanged = (e) => {
var NewValue = parseFloat(e.target.value);
setWorkhrsValue(NewValue);
var remaininghrsValue=(parseFloat(actualhrs)-parseFloat(workhrs));
setRemainhrsValue(remaininghrsValue);
}
const onSubmit = async (data) => {
console.log(data);
<FormProvider {...formvalues}>
<form
noValidate
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col md:overflow-hidden"
>
<Controller
render={({ field }) => (
<TextField
{...field}
id="actualhrs"
autoFocus={true}
variant="outlined"
placeholder="actualhrs"
/>
)}
name="actualhrs"
defaultValue="5"
control={control}
/>
<Controller
render={({ field }) => (
<TextField
{...field}
id="workhrs"
autoFocus={true}
variant="outlined"
placeholder="workhrs"
onChange={e => {
handleTextChanged(e)
field.onChange(e)
}}
/>
)}
name="workhrs"
defaultValue=""
control={control}
/>
<Controller
render={({ field }) => (
<TextField
{...field}
id="remainhrs"
autoFocus={true}
variant="outlined"
placeholder="remainhrs"
/>
)}
name="remainhrs"
defaultValue=""
control={control}
/>
</form>
</FormProvider>
ExpectedResult
I need to pass the Textfield3(remainhrs) value to submit event. But remainhrs value always coming undefined. How to resolve that.

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