I am using react-google-autocomplete with react-hook-form:
And when I try to add options parameters to the Component (which should filter my API predictions down to my country, and only cities, but it's clearly not working:
const { register, handleSubmit , control} = useForm();
<Controller
name="location"
rules={{
required: "This is a required field"
}}
control={control}
render={({ field, fieldState }) => (
<PlaceAutoComplete
{...field}
error={fieldState.error}
/>
)}
/>
And the autocomplete component:
import GooglePlacesAutocomplete from "react-google-places-autocomplete";
export const PlaceAutoComplete = ({ error, ...field }) => {
return (
<div>
<GooglePlacesAutocomplete
apiKey="APIKEY"
apiOptions={{
types: ["(cities)"],
componentRestrictions: { country: "hu" },
}}
selectProps={{
...field,
isClearable: true,
}}
/>
{error && <div style={{ color: "red" }}>{error.message}</div>}
</div>
);
};
export default PlaceAutoComplete;
When I check the network log, you can see no options added to the query:
Request URL: https://maps.googleapis.com/maps/api/place/js/AutocompletionService.GetPredictionsJson?1sLondon&4shu-HU&15e3&21m1&2e1&callback=_xdc_._dpoln7&key=APIKEY&token=111550
When I try to search for London, there should be no option, because I restrict to 'hu'- Hungary and only city:
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 wanted to have a input for location in my form like below
I am using a ready made component for this https://www.npmjs.com/package/react-google-places-autocomplete
import React from "react";
import GooglePlacesAutocomplete from "react-google-places-autocomplete";
const GooglePlacesAutocompleteComponent = () => (
<div>
<GooglePlacesAutocomplete
apiKey="xxxxxxxxxxxxxxx"
/>
</div>
);
export default Component;
What I generally do the following with react hook form for with a material ui Textfield is:
const validationSchema = Yup.object().shape({
location: Yup.string().required("Location is required"),
});
const {
control,
handleSubmit,
formState: { errors },
reset,
setError,
} = useForm({
resolver: yupResolver(validationSchema),
});
and materialui textfield
<Controller
name="location"
control={control}
render={({ field: { ref, ...field } }) => (
<TextField
{...field}
inputRef={ref}
fullWidth
label="location"
margin="dense"
error={errors.location ? true : false}
/>
)}
/>
<Typography variant="inherit" color="textSecondary">
{errors.name?.message}
</Typography>
So Here instead of TextField I have to use GooglePlacesAutocompleteComponent
I want user to know its required.
I think something like below should be possible, but I am not getting what props to pass:
<Controller
name="location"
control={control}
render={({ field: { ref, ...field } }) => (
<GooglePlacesAutocompleteComponent
<--------------------------->
But for this component how can i pass the below things
{...field}
inputRef={ref}
fullWidth
label="location"
margin="dense"
error={errors.location ? true : false}
<--------------------------->
/>
)}
/>
<Typography variant="inherit" color="textSecondary">
{errors.name?.message}
</Typography>
GooglePlacesAutocomplete uses react-select internally. In RHF docs, it shows you how to integrate with the Select component from react-select:
<Controller
name="iceCreamType"
control={control}
render={({ field }) => <Select
{...field}
options={[
{ value: "chocolate", label: "Chocolate" },
{ value: "strawberry", label: "Strawberry" },
{ value: "vanilla", label: "Vanilla" }
]}
/>}
/>
GooglePlacesAutocomplete exposes a SelectProps prop to let you override the Select props, so this is how you use it with RHF:
const GooglePlacesAutocompleteComponent = ({ error, ...field }) => {
return (
<div>
<GooglePlacesAutocomplete
apiKey="xxxxxxxxxxxxxxx"
selectProps={{ ...field, isClearable: true }}
/>
{error && <div style={{ color: "red" }}>{error.message}</div>}
</div>
);
};
And in your form:
<Controller
name="location"
rules={{
required: "This is a required field"
}}
control={control}
render={({ field, fieldState }) => (
<GooglePlacesAutocompleteComponent
{...field}
error={fieldState.error}
/>
)}
/>
How to add validation, I want show error message on for phone number field (helperText, min 10 , max 10 ) and also show error message if submitted without 10 numeric digit
import ReactPhoneInput from "react-phone-input-2"
import {TextField,Button}from "#material-ui/core"
const {register, handleSubmit,formState: { errors }} = useForm()
const getData= (data) => {
console.log(data.username)
console.log(data.phone);
}
<form onSubmit={handleSubmit(getData)} >
<Controller
control={control}
name="phone"
render={({ field: { ref, ...field } }) => (
<ReactPhoneInput {...field}
inputExtraProps={{ref, required: true, autoFocus: true }}
country={"in"}
onlyCountries={["in"]}
countryCodeEditable={false}
specialLabel={"Player Mobile Number"}
/>
)}
/>
<Button type='submit>Submit</Button>
</form>
You can use the rules prop of the <Controller /> component to define your validation rules. Check the rules section here for more info.
To display the errors you have to use formState object returned by useForm.
export default function App() {
const {
control,
handleSubmit,
formState: { errors }
} = useForm();
const onSubmit = (data) => {
console.log(data);
};
const isNumber = (number) => !isNaN(number) || "Must be a number";
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="phone"
rules={{
required: { value: true, message: "Required" },
minLength: { value: 12, message: "Min Length" },
maxLength: { value: 12, message: "Max Length" },
validate: isNumber
}}
render={({ field: { ref, ...field } }) => (
<ReactPhoneInput
{...field}
inputExtraProps={{
ref,
required: true,
autoFocus: true
}}
country={"in"}
onlyCountries={["in"]}
countryCodeEditable={false}
specialLabel={"Player Mobile Number"}
/>
)}
/>
{errors.phone && <p>{errors.phone.message}</p>}
<input type="submit" />
</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.
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