I am trying to load async data and use it to populate material-ui components in a form with react-hook-form. I have a TextField that seems to work fine, but I can't seem to figure out how to get the Select to show the correct value.
Here's a codesandbox to demo my problem.
I am using Controller to manage the Select as seems to be recommended in the docs:
const { register, handleSubmit, control, reset, setValue } = useForm()
<TextField name="name" inputRef={register} />
<Controller
name="color_id"
control={control}
register={register}
setValue={setValue}
as={
<Select>
{thingColors.map((tc, index) => (
<MenuItem key={index} value={tc.id}>
{tc.name}
</MenuItem>
))}
</Select>
}
/>
I'm trying to populate the fields with reset from useForm(), which seems to work for the TextField.
useEffect(() => {
getData().then((result) => {
reset({
color_id: 3,
name: 'Bill'
});
});
}, [reset]);
This seems to correctly set the values for the form, and when I submit my form it seems to have the correct values for name and for color_id. It seems like I'm not correctly hooking up the Select and the control is not showing the selected value that I set.
How can I get my material UI Select to show my applied value here?
In the version 7 of react hook form you can use setValue() setvalue API
useEffect(() => {
getData().then((result) => {
setValue('color_id', '3', { shouldValidate: true })
setValue('name', 'Bill', { shouldValidate: true })
});
}, []);
Note than I use the shouldValidate,this is becuase I use the isValidated in the button like this:
<Button
handler={handleSubmit(handlerSignInButton)}
disable={!isValid || isSubmitting}
label={"Guardar"}
/>
With shouldValidate I revalidate the inputs, There is also isDirty.
In version 7 of react hook form, you should use render instead of Controller API
<Controller
control={control}
name="test"
render={({
field: { onChange, onBlur, value, name, ref },
fieldState: { invalid, isTouched, isDirty, error },
formState,
}) => (
<Checkbox
onBlur={onBlur}
onChange={onChange}
checked={value}
inputRef={ref}
/>
)}
/>
Or you can use reset reset API
useEffect(() => {
getData().then((result) => {
reset({
'color_id': '3',
'name': 'Bill'
)
});
}, []);
I have not used Material UI with react hook form, but hope this is helpful.
A example of my select component, in Ionic React Typescript:
import { ErrorMessage } from "#hookform/error-message";
import { IonItem, IonLabel, IonSelect, IonSelectOption } from
"#ionic/react";
import { FunctionComponent } from "react";
import { Controller } from "react-hook-form";
type Opcion = {
label: string;
value: string;
};
interface Props {
control: any;
errors: any;
defaultValue: any;
name: string;
label: string;
opciones: Opcion[];
}
const Select: FunctionComponent<Props> = ({
opciones,
control,
errors,
defaultValue,
name,
label
}) => {
return (
<>
<IonItem className="mb-4">
<IonLabel position="floating" color="primary">
{label}
</IonLabel>
<Controller
render={({ field: { onChange, value } }) => (
<IonSelect
value={value}
onIonChange={onChange}
interface="action-sheet"
className="mt-2"
>
{opciones.map((opcion) => {
return (
<IonSelectOption value={opcion.value}
key={opcion.value}
>
{opcion.label}
</IonSelectOption>
);
})}
</IonSelect>
)}
control={control}
name={name}
defaultValue={defaultValue}
rules={{
required: "Este campo es obligatorio",
}}
/>
</IonItem>
<ErrorMessage
errors={errors}
name={name}
as={<div className="text-red-600 px-6" />}
/>
</>
);
};
export default Select;
And its implementation:
import React, { useEffect } from "react";
import Select from "components/Select/Select";
import { useForm } from "react-hook-form";
import Server from "server";
interface IData {
age: String;
}
let defaultValues = {
age: ""
}
const rulesEdad= {
required: "Este campo es obligatorio",
}
const opcionesEdad = [
{value: "1", label: "18-30"},
{value: "2", label: "30-40"},
{value: "3", label: "40-50"},
{value: "4", label: "50+"}
]
const SelectExample: React.FC = () => {
const {
control,
handleSubmit,
setValue,
formState: { isSubmitting, isValid, errors },
} = useForm<IData>({
defaultValues: defaultValues,
mode: "onChange",
});
/**
*
* #param data
*/
const handlerButton = async (data: IData) => {
console.log(data);
};
useEffect(() => {
Server.getUserData()
.then((response) => {
setValue('age', response.age, { shouldValidate: true })
}
}, [])
return (
<form>
<Select control={control} errors={errors}
defaultValue={defaultValues.age} opciones={opcionesEdad}
name={age} label={Edad} rules={rulesEdad}
/>
<button
onClick={handleSubmit(handlerSignInButton)}
disable={!isValid || isSubmitting}
>
Guardar
</button>
</form>
In React Hook Form the Select field have a "key/value" response.
So you should use:
setValue(field-name, {label: 'your-label' , value: 'your-value'});
Referring to https://github.com/react-hook-form/react-hook-form/discussions/8544
You need the Select to be wrapped with Controller and be sure to put a defaultValue on the Controller.
Example: https://codesandbox.io/s/admiring-curie-stss8q?file=/src/App.js
You can do something like this:
const Form: FC = () => {
const { register, handleSubmit, control, reset, setValue } = useForm();
const [color, setColor] = useState({name:"", color_id:-1})
useEffect(() => {
getData().then((result) => {
console.log("Got thing data", { result });
reset({
color_id: result.optionId,
name: result.name
});
setColor( {color_id: result.optionId,
name: result.name});
});
}, [reset]);
const onSubmit = (data: any) => console.log("Form submit:", data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div style={{ width: "200px" }}>
<div>
<TextField
fullWidth
name="name"
placeholder="Name"
inputRef={register}
/>
</div>
<div>
<Controller
name="color_id"
control={control}
register={register}
setValue={setValue}
defaultValue={color.name}
as={
<Select value="name" name="color_id" fullWidth>
{thingColors.map((tc, index) => (
<MenuItem key={index} value={tc.id}>
{tc.name}
</MenuItem>
))}
</Select>
}
/>
</div>
<p></p>
<button type="submit">Submit</button>
</div>
</form>
);
};
you can use a useState() to control the default value that you fetch with the getData() method and then pass the state to defaultValue param in the Controller.
Related
How can I reset MUI Autocomplete form with React Hook Form on submit? Just reset() doesn't work with Autocomplete.
Codesandbox: https://codesandbox.io/s/custom-autocomplete-8dh17l?file=/src/App.tsx
export default function App() {
const {
handleSubmit,
control,
reset,
formState: { isDirty, isValid }
} = useForm<FormValues>({
mode: "onChange",
defaultValues: {
name: "",
dataset: []
}
});
const onSubmit = (data: any) => {
console.log("data:", data);
reset();
};
return (
<div>
<Stack onSubmit={handleSubmit(onSubmit)}>
<Controller
render={({ field: { onChange } }) => {
return (
<CustomAutocomplete
multiple
getOptionLabel={(option: Option) => option.name}
options={DATA}
label={"name"}
onChange={(e, options: Option[]) => onChange(options)}
/>
);
}}
name="dataset"
control={control}
rules={{
required: "Field is required"
}}
/>
<Button
type={"submit"}
disabled={!isDirty || !isValid}
>
Submit
</Button>
</Stack>
</div>
);
}
Try adding value to the CustomAutocomplete component. This passes the value to autocomplete field every time the value in controller gets updated
<Controller
render={({ field: { onChange, value } }) => {
return (
<CustomAutocomplete
multiple
getOptionLabel={(option: Option) => option.name}
options={DATA}
label={"name"}
value={value}
onChange={(e: FocusEvent, options: Option[]) =>
onChange(options)
}
/>
);
}}
name="dataset"
control={control}
rules={{
required: "Field is required"
}}
/>
I'm trying to figure out how to use Material-UI Autocomplete with Formik.
Here I've created a form which displays 3 question fields per page, which is working fine.
Since I'm very new to this and don't really understand Formik very well I'm having trouble with this type of input.
So what I'm doing here is that first there is a condition, you select (yes or no) depending on if you want to answer the question. After that there is the Autocomplete dropdown, from which you can select multiple options. The part where I'm having trouble is I don't know how to submit multiple selected options. The options you select from the autocomplete dropdown will go into the remedies array.
In the end it should return something like this:
question11: {
agree: 'Yes',
remedies: ['option you selected', 'another selected option']
}
So I want to be able to select multiple values from Autocomplete and populate them in the remedies: [] array.
The FormikControl is another thing I made which is just a bunch of switch statements which render a certain type of Input depending on the condition that is passed. In this case its returning the Input Component I posted below.
The form:
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { Formik, Form } from 'formik';
import * as yup from 'yup';
import FormikControl from './FormikControl';
import SurveyFormLayout from '../surveyForm/SurveyFormLayout';
import PrimaryBtn from '../../../components/buttons/PrimaryBtn';
const data = [
{
type: 'radioWithDropdown',
question: 'Do you take any natural remedies? (Vitamins, minerals, amino acids,
herbs etc.)',
name: 'question11',
conditions: [ { key: 'yes', value: 'Yes' }, { key: 'no', value: 'No' } ],
options: [
{ key: 'Option 11', value: 'word' },
{ key: 'Option 12', value: 'another word' },
{ key: 'Option 13', value: 'some other' },
{ key: 'Option 14', value: 'random' }
]
}
]
const InputAssesment = () => {
const initialValues = {
question11: {
agree: '',
remedies: []
}
};
const validationSchema = yup.object({
question11: yup.object().shape({
agree: yup.string(),
remedies: yup.array()
})
});
return (
<SurveyFormLayout>
<div className="survey-form__container">
<FormikStepper
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={() => {}}
>
<FormikStep>
<FormikControl
control="conditionWithSelectMultiple"
label={data[9].question}
name="question11.remedies"
options={data[9].options}
conditionName="question11.agree"
conditionOptions={data[9].conditions}
/>
</FormikStep>
</FormikStepper>
</div>
</SurveyFormLayout>
)
}
export function FormikStep({ children }) {
return <div className="formik-step">{children}</div>;
}
export function FormikStepper({ children, ...props }) {
const childrenArray = React.Children.toArray(children);
const [ step, setStep ] = useState(0);
const currentChild = childrenArray[step];
function isLastStep() {
return step === childrenArray.length - 1;
}
return (
<Formik
{...props}
onSubmit={async (values, helpers) => {
if (isLastStep()) {
await props.onSubmit(values, helpers);
} else {
setStep((s) => s + 1);
}
console.log(values);
}}
>
<Form autoComplete="off" className="formik-form">
{currentChild}
<div className="survey-btns-container">
{step > 0 ? (
<button
className="btn-secondary survey-back-btn"
type="button"
onClick={() => setStep((s) => s - 1)}
>
Back
</button>
) : null}
<PrimaryBtn text={isLastStep() ? 'Submit' : 'Next'} type="submit" />
</div>
</Form>
</Formik>
);
}
The input component:
import React from 'react';
import { Field } from 'formik';
import Checkbox from '#material-ui/core/Checkbox';
import TextField from '#material-ui/core/TextField';
import { Autocomplete } from 'formik-material-ui-lab';
import CheckBoxOutlineBlankIcon from '#material-ui/icons/CheckBoxOutlineBlank';
import CheckBoxIcon from '#material-ui/icons/CheckBox';
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
const ConditionWithSelectMultiple = (props) => {
const { label, name, options, conditionName, conditionOptions, ...rest } = props;
return (
<div className="question-field">
<label className="survey-question-h">{label}</label>
<div className="radio-options-container">
<Field name={conditionName} {...rest}>
{({ field }) => {
return conditionOptions.map((option) => {
return (
<div key={option.key} className="radio-button-option">
<input
className="radio-button"
type="radio"
id={option.value}
{...field}
value={option.value}
checked={field.value === option.value}
/>
<div className="radio-button-gap" />
<label htmlFor={option.value} className="radio-button-option-label">
{option.value}
</label>
</div>
);
});
}}
</Field>
</div>
<Field
name="name"
component={Autocomplete}
multiple
options={options}
disableCloseOnSelect
getOptionLabel={(option) => option.key}
renderOption={(option, { selected }) => (
<React.Fragment>
<Checkbox icon={icon} checkedIcon={checkedIcon} style={{ marginRight: 8 }} checked={selected} />
{option.key}
</React.Fragment>
)}
style={{ width: 500 }}
renderInput={(params) => <TextField {...params} variant="outlined" placeholder="Select" />}
/>
</div>
);
};
export default ConditionWithSelectMultiple;
Any help is more than welcome
I'm trying to use React Select with useForm and got stuck when submitting form values.
My goal is a multi value react select with a default value and onChange function to do some validations when changing (like limit the number of itens to 3).
I already tried to search the solution in others posts and did make some changes in my code but unfortunately I did not succeed.
Everything seems to work perfectly but when I submit the form, my controller results in undefined value.
import React, {useEffect, useState, useContext} from 'react'
import {useForm, Controller} from 'react-hook-form'
import axios from 'axios';
import Select from 'react-select';
import BeeUtils from '../../../utils/BeeUtils'
export default function EditCategory2({place, goBack}){
var messageFieldRequired = 'Campo Obrigatório';
const audienceOptions = [
{ value: 'Lésbicas', label: 'Lésbicas' },
{ value: 'Gays', label: 'Gays' },
{ value: 'Bissexuais', label: 'Bissexuais' },
{ value: 'Transexuais', label: 'Transexuais' },
{ value: 'Queer', label: 'Queer' },
{ value: 'Intersexo', label: 'Intersexo' },
{ value: 'Assexual', label: 'Assexual' },
{ value: 'Héteros', label: 'Héteros' },
{ value: 'Todxs', label: 'Todxs' }
]
const handleAudienceSelector = (e) => {
console.log('OK');
console.log(e);
if(e.length > 3){
e.pop();
alert('max of 3 selected');
}
}
const {register , handleSubmit, errors, setValue, getValues, setError, control} = useForm();
const requestUpdate = async (data) => {
data.createdBy = place.createdBy;
data._id = place._id;
data.recordUpdatedType = 'audience';
console.log(data);
return;
}
const selectRequired = (e) => {
console.log(e);
console.log('OK-2');
//var error = e.length == 0? messageFieldRequired : '';
//return error;
}
const onSubmit = data => {
console.log(data)
requestUpdate(data);
}
return (
<div className='cad-form'>
<form onSubmit={handleSubmit(onSubmit)}>
<div className='cad-tit-container'>
<span className='cad-titulo'> Edit Here</span>
</div>
<div className='cad-container'>
<label htmlFor='test-audience'>Audience</label>
<Controller
name="test-audience"
control={control}
rules={{ validate: selectRequired }}
render={() => (
<Select
defaultValue={[audienceOptions[0], audienceOptions[1]]}
isMulti
onChange={handleAudienceSelector}
placeholder='Select Itens'
options={audienceOptions}
className="basic-multi-select selectCustom"
classNamePrefix="select"
/>
)}
/>
{errors?.targetAudience && <p>{errors.targetAudience.message}</p>}
</div>
<div className='btn-container'>
<div className='cad-btn'><button onClick={(e) => goBack('initial')} className="btn waves-effect yellow darken-2">Voltar</button></div>
<div className='cad-btn'><button type='submit' className="btn waves-effect yellow darken-2">Salvar Alterações</button></div>
</div>
</form>
</div>
)
}
After some changes (thanks to help of the answer) I tried this code
import React, {useEffect, useState, useContext} from 'react'
import {useForm, Controller} from 'react-hook-form'
import axios from 'axios';
import Select from 'react-select';
import BeeUtils from '../../../utils/BeeUtils'
export default function EditCategory2({place, goBack}){
var messageFieldRequired = 'Campo Obrigatório';
const audienceOptions = [
{ value: 'Lésbicas', label: 'Lésbicas' },
{ value: 'Gays', label: 'Gays' },
{ value: 'Bissexuais', label: 'Bissexuais' },
{ value: 'Transexuais', label: 'Transexuais' },
{ value: 'Queer', label: 'Queer' },
{ value: 'Intersexo', label: 'Intersexo' },
{ value: 'Assexual', label: 'Assexual' },
{ value: 'Héteros', label: 'Héteros' },
{ value: 'Todxs', label: 'Todxs' }
]
const handleAudienceSelector = (e) => {
console.log('OK');
console.log(e);
if(e.length > 3){
e.pop();
alert('max of 3 selected');
}
}
const {register , handleSubmit, errors, setValue, getValues, setError, control} = useForm();
const requestUpdate = async (data) => {
data.createdBy = place.createdBy;
data._id = place._id;
data.recordUpdatedType = 'audience';
console.log(data);
return;
}
const onSubmit = data => {
console.log(data)
requestUpdate(data);
}
return (
<div className='cad-form'>
<form onSubmit={handleSubmit(onSubmit)}>
<div className='cad-tit-container'>
<span className='cad-titulo'> Edit Here</span>
</div>
<div className='cad-container'>
<label htmlFor='test-audience'>Audience</label>
<Controller
name="targetAudience"
control={control}
defaultValue={[audienceOptions[0], audienceOptions[1]]}
rules={{ required: messageFieldRequired }}
render={({ field: { onChange, value } }) => (
<Select
value={value}
onChange={onChange}
isMulti
placeholder="Select Itens"
options={audienceOptions}
className="basic-multi-select selectCustom"
classNamePrefix="select"
/>
)}
/>
</div>
<div className='btn-container'>
<div className='cad-btn'><button onClick={(e) => goBack('initial')} className="btn waves-effect yellow darken-2">Voltar</button></div>
<div className='cad-btn'><button type='submit' className="btn waves-effect yellow darken-2">Salvar Alterações</button></div>
</div>
</form>
</div>
)
}
But now I got the error: TypeError: Cannot read property 'onChange' of undefined
The reason why it isn't working is because you forgot to to link the <Controller /> component with the <Select /> component via the value and onChange properties of the render prop function from <Controller />.
<Controller
name="targetAudience"
control={control}
defaultValue={[audienceOptions[0], audienceOptions[1]]}
rules={{ required: "Campo obrigatório", validate: isOnly3Values }}
render={({ field: { onChange, value } }) => (
<Select
value={value}
onChange={onChange}
isMulti
placeholder="Select Itens"
options={audienceOptions}
className="basic-multi-select selectCustom"
classNamePrefix="select"
/>
)}
/>
You also don't need to use useState here for handling the error state as RHF already provides this functionality. For setting a field to be required, you can just set the required property of the validation object which can be passed to <Controller /> via the rules prop. Check here for more information about <Controller />. I would suggest to also use the validate function from RHF to check if the user added more than 3 items to your <Select /> and display an error message instead of using an alert.
I made some overall small changes and corrected some minor issues (e.g. the errors object is located in the formState property since v7). Feel free to write a comment if something isn't clear.
I'm using react-hook-forms Controller api around AsyncSelect from react-select to load options as the user types from an external API. Everything works fine except the returned value is coming back as the string "[object Object]" instead of the fullName property from the object.
My component:
<Controller
control={control}
name="businessCategory"
as={
<AsyncSelect
className="react-select-container"
loadOptions={v => handleAutocompleteLookup(v)}
onChange={handleCategoryInputChange}
getOptionLabel={option => option.name}
getOptionValue={option => option.fullName}
/>
}
/>
My handleChange function. SetValue is from react-hook-form:
const handleCategoryInputChange = newValue => {
return setValue('businessCategory', newValue, true);
};
Any my data is an array of objects with the following shape:
{
fullName: "DJ service"
id: "gcid:dj"
name: "DJ service"
publisher: "GMB"
}
Any clues on this would be appreciated, thank you!
Update your code in following way
In your import
import { useForm, Controller } from 'react-hook-form';
import Select from 'react-select';
In your hook component
function Yourcomponent(props){
const methods = useForm();
const { handleSubmit } = methods;
const options = [
{ value: '1', label: 'Apple'},
{ value: '2', label: 'Ball'},
{ value: '3', label: 'Cat'},
];
const default_value = 1; // you can replace with your default value
// other codes of the component
function submitHandler(formData){
// values are available in formData
}
return(
<div>
{* other part of your component *}
<form onSubmit={handleSubmit(submitHandler)} >
{* other part of your form *}
<Controller
control={methods.control}
defaultValue={default_value}
name="field_name_product"
render={({ onChange, value, name, ref }) => (
<Select
inputRef={ref}
classNamePrefix="addl-class"
options={options}
value={options.find(c => c.value === value)}
onChange={val => onChange(val.value)}
/>
)}
/>
{* other part of your form *}
</form>
{* other part of your component *}
</div>
)
}
For multi-select (works with react-hook-form v7):
import Select from "react-select";
import { useForm, Controller } from "react-hook-form";
const {
control
} = useForm();
<Controller
control={control}
defaultValue={options.map(c => c.value)}
name="options"
render={({ field: { onChange, value, ref }}) => (
<Select
inputRef={ref}
value={options.filter(c => value.includes(c.value))}
onChange={val => onChange(val.map(c => c.value))}
options={options}
isMulti
/>
)}
/>
I made it work like this:
const options = [
{value: '', label: ''}
]
import { useForm, Controller } from "react-hook-form";
import { ErrorMessage } from "#hookform/error-message";
import Select from "react-select";
const methods = useForm();
<section>
<label>Label</label>
<Controller
as={Select}
name="Name"
options={options}
isMulti
control={methods.control}
/>
<ErrorMessage errors={methods.errors} name="Name" />
</section>
well, actually most of these answers don't work for situations where you have a custom onChange function. The trick (seems like a bug) is to set the "onChange(val.value)" inline first and then set your state variable. Here is the full code I have that works;
import { useForm, Controller } from "react-hook-form";
const {
register,
control,
formState: { errors },
handleSubmit,
} = useForm();
const handleChangeType = (option) => {
setItemType(option);
var options = getOptions(option.value);
setList(options);
setGender(null);
};
<Controller
control={control}
name="itemType"
rules={{
required: {
value: assetType.value == "item",
message: "Item type is required.",
},
}}
render={({ field: { onChange, value, ref, name } }) => (
<Select
className={"react-select"}
classNamePrefix={"react-select"}
placeholder={"Item type"}
options={itemTypeList}
onChange={val => {
onChange(val.value);
handleChangeType(val);
}}
/>
)}
/>
{errors.item?.message && <div class="validationText">{errors.item?.message}</div>}
import Select from "react-select";
import { useForm, Controller } from "react-hook-form";
const options = [
{ value: '1', label: 'Apple'},
{ value: '2', label: 'Ball'},
{ value: '3', label: 'Cat'},
];
const {control} = useForm();
<Controller
control={control}
name="AnyName"
render={({ field: { onChange, value, name, ref } }) => (
<Select
inputRef={ref}
classNamePrefix="addl-class"
options={options}
value={options.find((c) => c.value === value)}
onChange={(val) => onChange(val.map((c) =>c.value))}
isMulti
/>
)}
/>
Fixed this by approaching it with a custom register via react-hook-form as seen here:
https://react-hook-form.com/get-started#Registerfields
I wanted to post what worked for me. I used react-select, react-hook-form, and #hookform/error-message:
I created a function as follows
const handleCategoryInputChange = (newValue: any) => {
console.log(newValue.value);
return setValue('contract', newValue.value);
};
My react select looks like this:
<Controller
name="contract"
control={control}
rules={{ required: true }}
render={({ field: { onChange, value, name, ref } }) => (
<ReactSelect
// value={contractData.find((c) => c.value === value)}
// onChange={(val) => onChange(val)}
onChange={handleCategoryInputChange}
options={contractData}
ref={ref}
name={name}
defaultValue={contractData[0]}
theme={customTheme}
isSearchable={false}
className="block w-full min-w-0 flex-1 sm:text-sm"
/>
)}
/>
<ErrorMessage errors={errors} name="contract" message="Select a Contract" />
I don't think my className is working properly you can ignore that part. It is working for my specific set up though lol. The commented out code did not work the way I wanted because when i console logged the data I got it as follow:
contract: {label: 'contract name', value: 'contract name'}
optionPeriods: "dfd"
performanceEndDate: "2022-12-08"
performanceStartDate: "2022-12-08"
proposalDueDate: "2022-12-08T12:54"
requirements: "dfd"
trackingNumber: "dfd"
so I commented out that specific solution and did it the way you see now and i get the exact data i want for my contract as you can see below:
contract: "contract name"
optionPeriods: "dfd"
performanceEndDate: "2022-12-08"
performanceStartDate: "2022-12-08"
proposalDueDate: "2022-12-08T12:54"
requirements: "dfd"
trackingNumber: "dfd"
I hope this helps
I'm using Material UI Select as a reusable component, and I want to validate it with react-hook-form by passing props from parent to child component. So far, I've tried to use from RHF, and to pass some props to the child, but somehow error wont disappear when I select option. This is my code
import React from 'react';
import styled from '#emotion/styled';
import { Select, MenuItem } from '#material-ui/core';
import { ASSelect } from '../../ASSelect';
import { Controller, useForm } from 'react-hook-form';
const { register, handleSubmit, watch, errors, control, setValue } = useForm();
const onSubmit = (data: any) => {
console.log(errors);
};
const defaultDashboard = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
const Parent = () => {
return (
<Controller
as={
<ASSelect
menuItems={defaultDashboard}
label="Default dashboard*"
handleChange={dashboardHandler}
value={dashboardValue}
}
name="Select"
control={control}
rules={{ required: true }}
onChange={([selected]) => {
return { value: selected };
}}
/>
{errors.Select ? <span>Default dashboard is required</span> : null}
)
}
export {Parent};
const ASSelect = ({menuItems, label, handleChange, value}) => {
return (
<div>
<Select>
{menuItems.map((el, index)=> {
return <MenuItem key={index}>{el.label}</MenuItem>
}}
</Select>
</div>
)}
export {ASSelect};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
So when I submit a form without selecting anything, the error will popup. But when I select an option, the error will remain there. What am I doing wrong?
You can use Select with Controller from react-hook-forms in the following way:
const SelectController = ({ name, menuItems, control }) => (
<Controller
as={
<Select>
{menuItems.map(({ value, label }, index) => (
<MenuItem key={index} value={value}>
{label}
</MenuItem>
))}
}
</Select>
}
name={name}
control={control}
defaultValue=""
rules={{ required: true }}
/>
);
or use Select with setValue from react-hook-forms as follows:
const SelectSetValue = ({ name, menuItems, setValue }) => {
return (
<Select
name={name}
defaultValue=""
onChange={e => setValue(name, e.target.value)}
>
{menuItems.map(({ value, label }, index) => (
<MenuItem key={index} value={value}>
{label}
</MenuItem>
))}
}
</Select>
);
};
In both cases you get error-validation initially onSubmit event. Error display is updated afterwards for SelectController when you select a value and for SelectSetValue when you select a value and re-trigger onSubmit.
You can check a working example at following the link:
https://codesandbox.io/s/somaterialselecthookform-kdext?file=/src/App.js