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}
/>
)}
/>
Related
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>
)
}}
/>
I have the following controlled component set up using react-hook-forms.
<Controller
control={control}
name={"test"}
render={({ field: { onChange, value, name } }) => (
<Dropdown
name={name}
value={value}
handleChange={onChange}
options={foodCategories()}
/>
)}
/>
I want to call a debounce and and I tried doing the following:
handleChange={debounce(onChange, 500)}
but I keep getting errors throw:
This synthetic event is reused for performance reasons, Objects are not valid as a React child (found: object with keys {dispatchConfig, _targetInst, nativeEvent, type, target, currentTarget, eventPhase, bubbles, cancelable,
How can I call debounce on a controlled react hook form component?
This could be achieved my the following implementation;
<Controller
name={fieldName}
rules={{ required: `errors.${fieldName}.required` }}
render={({ field, fieldState: { error } }) => (
<input
type="text"
error={error && t(`${error.message}`)}
onChange={debounce((e) => field.onChange(e), 50)}
defaultValue={field.value}
/>
)}
/>
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" }
]}
/>
)}
/>
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>
);
}
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