How can I get option value from react select? - reactjs

My codes are as below, can you help me? Where am I doing wrong?
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
]
const { register, handleSubmit, watch, formState: { errors }, control } = useForm();
const onSubmit = (data, e) => {
e.preventDefault();
console.log(data);
}
I want to get the option data using react select, but the defaltvalue is coming, I can't get the option data at all.
<Controller
name='stocktype'
render={({ register, value }) => (
<Select
{...register}
options={options}
styles={customStyles}
noOptionsMessage={() => 'Aramanıza uygun değer bulunamadı'}
placeholder={'Stok Tipi Seçiniz'}
value={options.find((c) => c.value === value)}
/>
)}
control={control}
defaultValue={options[0]}
/>

You'll need to grab the controller's hooks and pass them to the Select component in order to execute the correct events to grab the data.
Do something like this:
<Controller
control={control}
name="test"
render={({
field: { onChange, onBlur, value, name, ref },
register
}) => (
<Select
{...register}
options={options}
styles={customStyles}
noOptionsMessage={() => 'Aramanıza uygun değer bulunamadı'}
placeholder={'Stok Tipi Seçiniz'}
value={value}
onChange={onChange} // send value to hook form
onBlur={onBlur} // notify when input is touched
/>
)}
/>
Notice the field attribute under render and how hooks are passed to the Select.

Related

how to use controller component in react-hook-form

I want to validate MuiPhoneInput using react hook form (v7.43.1).
The useForm looks like
const {
register,
handleSubmit,
formState: { errors },
control,
} = useForm();
The controller component looks like
<Controller
name="phone"
control={control}
render={({ field: { onChange, value } }) => (
<MuiPhoneInput
value={value}
onChange={onChange}
defaultCountry={"il"}
variant={"outlined"}
error={!!errors.phone}
helperText={errors?.phone?.message}
/>
)}
rules={{
required: "Phone is required",
}}
/>
But I can't write anything in the input. I tried with other textfields, but nothing works. How to solve this?
You're very close except your MuiPhoneInput is losing reference from the field destructure you're using to get value and onChange. There's an important ref that needs to be set contained inside field. Here's the official example of a Controller component handling a custom masked input.
Here's my working sandbox that demonstrates integrating material-ui-phone-material with react-hook-form.
Since you're setting defaultCountry={"il"} on the MUI component, also set the default value that resolves to inside defaultValues so react-hook-form knows about it.
Adds validation via isNotFilledTel() used inside rules>validate object.
Handling a React.StrictMode issue where ref.focus is undefined.
app.js
import { Controller, useForm } from "react-hook-form";
import MuiPhoneInput from "material-ui-phone-number";
export default function App() {
const {
handleSubmit,
formState: { errors },
control,
} = useForm({
reValidateMode: "onSubmit",
defaultValues: {
phone: "+972",
},
});
const isNotFilledTel = (v: string) => {
return v.length < 15 ? "Phone number is required." : undefined;
};
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<Controller
name="phone"
control={control}
rules={{
required: "Phone is required",
validate: {
inputTelRequired: isNotFilledTel,
},
}}
render={({ field }) => {
return (
<MuiPhoneInput
{...field}
ref={(ref) => {
if (ref && !ref.focus) ref.focus = () => {};
}}
defaultCountry={"il"}
variant={"outlined"}
error={!!errors.phone}
helperText={<>{errors?.phone?.message}</>}
/>
);
}}
/>
{errors.phone && <p>Phone is required.</p>}
<button type="submit">Submit</button>
</form>
);
}

How to reset MUI Autocomplete form on submit with React Hook Form?

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"
}}
/>

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.

How to set value of a Select in react-hook-form?

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.

Returning correct value using react-select and react-hook-form

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

Resources