Register third party- custom component with react-hook-form - reactjs

I am using react-hook-form and using third party DatePicker. Since it's a custom component using it as a controlled component to register it. This works fine
<Controller
control={control}
name="reviewStartDate"
render={({ field: { onChange, onBlur, value } }) => (
<DatePicker
className={`form-control ${errors.reviewStartDate ? 'is-invalid' : ''}`}
customInput={<input />}
wrapperClassName="datePicker"
onChange={onChange}
onBlur={onBlur}
selected={value ? new Date(value) : ''}
dateFormat='dd-MMM-yyyy'
/>
)}
/>
Similarly/however, I am using thirdparty Multiselect. Here the value is not being registered. It does show the selected value but when I submit the form the value is not present in data.
<Controller
control={control}
name="rootCauseAnalysisCategory"
render={({ field: { value } }) => (
<Multiselect
options={rootCauseAnalysisCategorys}
isObject={false}
showCheckbox={true}
hidePlaceholder={true}
closeOnSelect={false}
selectedValues={value}
/>
)}
/>
Similarly

The <MultiSelect /> component has onSelect and onRemove props, so you can just pass onChange to them. This will work because they both have the signature that the first argument is an array containing the current selected values.
<Controller
control={control}
name="rootCauseAnalysisCategory"
defaultValue={[]}
render={({ field: { value, onChange } }) => (
<Multiselect
options={rootCauseAnalysisCategorys}
isObject={false}
showCheckbox={true}
hidePlaceholder={true}
closeOnSelect={false}
onSelect={onChange}
onRemove={onChange}
selectedValues={value}
/>
)}
/>
UPDATE
If you want to access the current value for rootCauseAnalysisCategory, you have to use watch. Please note, that it is also important to either provide a defaultValue at the <Controller /> field level or call useForm with defaultValues. In the example i passed the defaultValue at the field level.
function App() {
const { control, handleSubmit, watch } = useForm();
const onSubmit = (data) => {
console.log(data);
};
const rootCauseAnalysisCategorys = ["Category 1", "Category 2"];
const rootCauseAnalysisCategory = watch("rootCauseAnalysisCategory");
return (
<div className="App">
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="rootCauseAnalysisCategory"
defaultValue={[]}
render={({ field: { value, onChange } }) => (
<Multiselect
options={rootCauseAnalysisCategorys}
isObject={false}
showCheckbox={true}
hidePlaceholder={true}
closeOnSelect={false}
onSelect={onChange}
onRemove={onChange}
selectedValues={value}
/>
)}
/>
{rootCauseAnalysisCategory?.includes("Category 1") && <p>Category 1</p>}
<input type="submit" />
</form>
</div>
);
}

Related

How to trigger re-render with `setValue` using react-hook-form?

I have a simple form with a select field, it's react-hook-form for validation and everything. There's a Controller which renders a Material UI Select. I would like to set the value of such select using setValue from outside the component (in the root of the form, where all controls reside).
This is the piece of code I'm using (slightly simplified not to waste too much of your time)
type Props = {
name: string;
control: Control<any>;
options: SelectOptions[];
};
const Select: FunctionComponent<Props> = ({
name,
control,
options,
}) => (
<Controller
control={control}
name={name}
render={({ field: { onChange, value } }) => {
return (
<FormControl>
<MuiSelect onChange={onChange}>
{options.map((o) => (
<MuiSelectItem key={o.key} value={o.value}>{o.label}</MuiSelectItem>
))}
</MuiSelect>
</FormControl>
)
}}
/>
);
As far as changing the value of the select, setValue works magically. When I feed a new value, it works as intended. The problem (I guess) is the component is not re-rendered, so the old value is still shown. I'm not sure how to fix this thing and docs did not help a lot. Any help is much appreciated, thanks!
As #knoefel said I tried setting the defaultValue="" but doesn't work for me(maybe because I am using FormProvider). So what I did is use watch instead of value
<Controller
name='name'
control={control}
render={({ field: { onChange } }) => (
<Select
dropdownValues={dropdownValues}
value={watch('name')}
onChange={onChange}
/>
)}
/>
I think you just forgot to set the value prop of <Controller /> to your <MuiSelect />. You also have to set a defaultValue for your <Controller /> field, either via the defaultValue prop or via useForm.
<Controller
control={control}
name={name}
defaultValue=""
render={({ field: { onChange, value } }) => {
return (
<FormControl>
<MuiSelect onChange={onChange} value={value}>
{options.map((o) => (
<MuiSelectItem key={o.key} value={o.value}>{o.label}</MuiSelectItem>
))}
</MuiSelect>
</FormControl>
)
}}
/>

How To implement ReactFlagsSelect with React Hook Form Controller

I tried to implement with React Hook From using <Controller />. While I am submitting the form country field return undefined.
<Controller
name="country"
control={control}
render={({ field: { onChange, value } }) => (
<ReactFlagsSelect
selected={selected}
onSelect={code => handleChange(code)}
value={value}
onChange={onChange}
/>
)}
/>
The problem is you pass RHF's onChange handler to the wrong prop. <ReactFlagsSelect /> doesn't have a onChange prop, instead you should pass it to the onSelect prop.
<Controller
name="country"
control={control}
render={({ field: { onChange, value } }) => (
<ReactFlagsSelect
selected={selected}
onSelect={onChange}
value={value}
/>
)}
/>
Side-note: RHF's will update value changes to your registered fields in it's internal form state, so there is no need to use extra state management for these values via useState or something similar. If you really need to call your handleChange callback, you can do both in the onSelect callback.
<Controller
name="country"
control={control}
render={({ field: { onChange, value } }) => (
<ReactFlagsSelect
selected={selected}
onSelect={code => {
onChange(code);
handleChange(code);
}}
value={value}
/>
)}
/>

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 Hook Form and React Select Not Working as Expected

Im trying to wrap React Select inside of a React Hook Form with the Controller wrapper component as per the docs (https://react-hook-form.com/get-started#IntegratingControlledInputs)
<Controller
name="ShiftCaptain"
control={control}
render={({ field }) => (
<Select
{...field}
value={selectValue}
options={options}
placeholder={"Select Person"}
onChange={(e) => setSelectValue(e)}
/>
)}
/>
On form submission, the value captured inside the React Select isnt being populated into React Hook Forms:
Any ideas?
TIA
The field object of the render callback which you spread on your <ReactSelect /> component has a value and a onChange property, which RHF needs to link the value and also changes to the value to it's internal form state. Right now you overwriting this, so RHF can't update the value. Try this and it should work:
<Controller
name="ShiftCaptain"
control={control}
defaultValue={null}
render={({ field }) => (
<Select
{...field}
options={options}
placeholder={"Select Person"}
/>
)}
/>
If you want to set a default value for your select after page load you should use the <Controller /> defaultValue prop, which will pass the value to your <ReactSelect />.
Please check the this code sandbox.
https://codesandbox.io/s/react-hook-form-v7-controller-forked-40t3s?file=/src/index.js
const handleOnChange = (e) => {
setSelectedValue(e.value);
};
<label>React Select</label>
<Controller
name="ReactSelect"
control={control}
render={({ field }) => (
<ReactSelect
isClearable
{...field}
value={selectedValue}
onChange={handleOnChange}
options={[
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
]}
/>
)}
/>

react-hook-form(V7): How to pass a function via onChange to a nested Material UI component

I am have a Material UI nested component that look as follow:
imports . . .
const TxtInput = ({ name, value, label, required }) => {
const { control, ...rest } = useFormContext()
return (
<Controller
name={name}
defaultValue={value}
control={control}
render={({
field: { onChange, onBlur, value, name, ref }
}) =>
<TextField
required={required}
fullWidth
label={label}
id={name}
inputProps={{ 'aria-label': label }}
onBlur={onBlur}
onChange={onChange}
checked={value}
inputRef={ref}
{...rest}
/>}
/>
)
}
export default TxtInput
While in my app.js, <TxtInput /> look like this:
<FormProvider {...methods}>
<form onSubmit={handleSubmit(submit)}>
<TxtInput
name='fullName'
label='First and last name'
required
value=''
onChange={() => console.log('hello')}
</form>
</FormProvider>
And I am expecting to see 'Hello' with every keystroke in my console but, that is not the case.
Does anyone know why?
I think what you want is to pass the onChange event to the TxtInput instead of using its own Controller onChange
const TxtInput = ({ name, value, label, required, onChange }) => { // add onChange here
const { control, ...rest } = useFormContext()
return (
<Controller
name={name}
defaultValue={value}
control={control}
render={({
field: { onBlur, value, name, ref } // remove onChange here to allow pass though from parent onChange
}) =>
<TextField
required={required}
fullWidth
label={label}
id={name}
inputProps={{ 'aria-label': label }}
onBlur={onBlur}
onChange={onChange}
checked={value}
inputRef={ref}
{...rest}
/>}
/>
)
}
make a codesandbox to simulate your case as well. You can check it out
https://codesandbox.io/s/runtime-hill-9q2qu?file=/src/App.js
The last nested onChanged function should be returned as ()=>onChangeFn

Resources