I am trying to validate the autocomplete multiple select using the formik. First time it is working on button click but when I remove the selected value then it is not validation.
import React from "react";
import ReactDOM from "react-dom";
import { Formik, Form } from "formik";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import Button from "#material-ui/core/Button";
import * as Yup from 'yup';
const cities = [{
state: "Illinois",
name: "Chicago",
id: 3,
}, {
state: "Texas",
name: "Houston",
id: 2
}, {
state: "California",
name: "Los Angeles",
id: 1
}, {
state: "New York",
name: "New York City",
id: 4
}];
const initialValues = {
city_id: '',
};
const submit = values => {
let city=JSON.parse(values.city_id);
};
const SignupForm = () => {
return (
<Formik initialValues={initialValues}
validationSchema = {
Yup.object().shape({
city_id: Yup.string().max(255).required('City is required')
})
}
onSubmit={submit}>
{({ handleChange,touched,errors, values, setFieldValue }) => (
<Form>
<Autocomplete
multiple
id="city_id"
name="city_id"
options={cities}
getOptionLabel={option => option.name}
style={{ width: 300 }}
onChange={(e, value) => {
console.log(value);
let val=JSON.stringify(value);
setFieldValue(
"city_id",
value !== null ? val : initialValues.city_id
);
}}
renderInput={params => (
<TextField
error={Boolean(touched.city_id && errors.city_id)}
helperText={touched.city_id && errors.city_id}
margin="normal"
label="Cities"
fullWidth
name="city_id"
{...params}
/>
)}
/>
<Button variant="contained" color="primary" type="submit">
Submit
</Button>
</Form>
)}
</Formik>
);
}
export default SignupForm;
Please provide better validation option if I have made mistake.
Validation will not run because value is never empty.
If you want to run validation, do:
setFieldValue(
"city_id",
value
);
above code will trigger validation because value will be "" when you remove.
Related
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 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 scratching my head over this one.
I'm using <FieldArray> from formik to display multiple <select> components and adding more fields.
Now while the adding more <select> works fine, I get this weird error in the console ...
Warning: The `value` prop supplied to <select> must be a scalar value if `multiple` is false.
And that only happens when using the <InputSelect> custom component inside <FieldArray>, But when I use the <InputSelect> by itself outside the <FieldArray> it works with no issue.
Here's a Codesandbox to illustrate this issue.
https://codesandbox.io/s/silly-water-z5wl3
Any help will be appreciated.
The problem seems to be that you try to pass an array of objects to the field (the languages). If you refactor it a little bit and move the possible languages into an external variable it works:
import React from "react";
import ReactDOM from "react-dom";
import { Formik, Form, Field, FieldArray } from "formik";
import uuid from "uuid";
import InputSelect from "./InputSelect";
import "./styles.css";
const LANGUAGES = [
{
key: "German",
value: "German"
},
{
key: "French",
value: "French"
},
{
key: "Japanese",
value: "Japanese"
},
{
key: "English",
value: "English"
}
];
function App() {
const initialValues = {
subtitles: [
{
language: "Japanese",
subtitleFile: ""
}
]
};
const handleFormSubmit = (values, bag) => {
console.log(values);
bag.setSubmitting(false);
};
return (
<div className="App">
<Formik initialValues={initialValues} onSubmit={handleFormSubmit}>
{({ isValid, isSubmitting, values }) => (
<Form>
<h3>This Works</h3>
<Field
label="Gender"
name="gender"
component={InputSelect}
options={[
{ key: "Male", value: "Male" },
{ key: "Female", value: "Female" }
]}
/>
<br />
<br />
<h3>This Doesn't</h3>
<FieldArray name="subtitles">
{({ push }) => (
<>
{values.subtitles.map((item, index) => {
console.log(item);
return (
<div className="twoColumnsGroup" key={uuid()}>
<Field
placeholder="Language"
name={`subtitles[${index}].language`}
component={InputSelect}
options={LANGUAGES}
/>
</div>
);
})}
<button
onClick={() =>
push({
language: "German",
subtitleFile: ""
})
}
>
Add Another
</button>
</>
)}
</FieldArray>
<div className="cta">
<button disabled={!isValid || isSubmitting} type="submit">
Save
</button>
</div>
</Form>
)}
</Formik>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Currently trying to use Material UI's Autocomplete component with Formik. So far things like text fields and traditional selects from Material-UI play very nice with Formik. Implementing Autocomplete is not the case. Formik's onChange handler doesn't seem to update the value for my city_id. I know Autocomplete is still not apart of Material-UI's core library but was still seeing if something like this was a possibility at the moment.
import React from "react";
import ReactDOM from "react-dom";
import { Formik, Form } from 'formik';
import TextField from '#material-ui/core/TextField';
import Autocomplete from '#material-ui/lab/Autocomplete';
import Button from '#material-ui/core/Button';
import { cities } from '../data/cities';
import "./styles.css";
const initialValues = {
city_id: '',
};
const submit = params => {
alert(`Value for city_id is: ${params.city_id}`);
};
function App() {
return (
<Formik
initialValues={ initialValues }
onSubmit={ submit }
>
{({
handleChange,
values,
}) => (
<Form>
<Autocomplete
id="city_id"
name="city_id"
options={ cities }
groupBy={ option => option.state }
getOptionLabel={ option => option.name }
style={{ width: 300 }}
renderInput={params => (
<TextField
{ ...params }
onChange={ handleChange }
margin="normal"
label="Cities"
fullWidth
value={ values.city_id }
/>
)}
/>
<Button
variant="contained"
color="primary"
type="submit"
>
Submit
</Button>
</Form>
)}
</Formik>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Your problem is that handleChange won't work the way you are doing.
If you take a look at the handleChange docs:
General input change event handler. This will update the values[key] where key is the event-emitting input's name attribute. If the name attribute is not present, handleChange will look for an input's id attribute. Note: "input" here means all HTML inputs.
Which should work fine, but the problem is that the TextField inside Autocomplete will only trigger handleChange when you type something on it, and the value will be the text, not the id or other property you want, so you need to move handleChange to the Autocomplete.
And there is another problem, you can't use handleChange in the Autocomplete because it doesn't references the input you want and it also have different parameters from the normal onChange of the input, as you can see in the docs.
onChange
func
Callback fired when the value changes.
Signature:
function(event: object, value: any) => void
event: The event source of the callback
value: null
So what you need to do is use setFieldValue and pass it to Autocomplete like
onChange={(e, value) => setFieldValue("city_id", value)}
You need to pass the name of your field and what value you want to get.
Here is a working example
#vencovsky has provided the correct answer that is still working for me with Material UI 14.10.1.
I'm adding a bit more to it as I have my field set to required in using Yup validation.
To get this to work correctly I have the following:
Yup config:
validationSchema = {
Yup.object().shape({
contact: Yup.string().max(255).required('Contact is required'),
})
}
react:
<Autocomplete
id="contact-autocomplete"
options={contacts}
getOptionLabel={(contact) => `${contact?.firstName} ${contact?.lastName}`}
onChange={(e, value) => setFieldValue("contact", value?.id || "")}
onOpen={handleBlur}
includeInputInList
renderInput={(params) => (
<TextField
{...params}
error={Boolean(touched.contact && errors.contact)}
fullWidth
helperText={touched.contact && errors.contact}
label="Contact Person"
name="contact"
variant="outlined"
/>
)}
/>
When the user click on the Autocomplete element, it fires the onOpen which runs the Formik onBlur and marks the field as touched. If an item is then not picked, Formik flags the field and displays the Contact is required validation message.
You have to add onChange = {(event, value) => handleChange(value)} in Autocomplete tag as
import React from "react";
import ReactDOM from "react-dom";
import { Formik, Form } from 'formik';
import TextField from '#material-ui/core/TextField';
import Autocomplete from '#material-ui/lab/Autocomplete';
import Button from '#material-ui/core/Button';
import { cities } from '../data/cities';
import "./styles.css";
const [cityId,setCityId]=React.useState({city_id:''});
const handleChange=(value)=>{
// Here is the value is a selected option label or the new typed value
setCityId({city_id:value});
}
function App() {
return (
<Formik
initialValues={ cityId }
onSubmit={() => {
alert(`Value for city_id is: ${cityId.city_id}`);
}}
>
{({
handleChange,
values,
}) => (
<Form>
<Autocomplete
id="city_id"
name="city_id"
options={ cities }
groupBy={ option => option.state }
getOptionLabel={ option => option.name }
style={{ width: 300 }}
onChange = {(event, value) => handleChange(value)}
renderInput={params => (
<TextField
{ ...params }
onChange={ handleChange }
margin="normal"
label="Cities"
fullWidth
value={ values.city_id }
/>
)}
/>
<Button
variant="contained"
color="primary"
type="submit"
>
Submit
</Button>
</Form>
)}
</Formik>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
If onChange don't work you can use onInputChange as well.
I had the same issue recently and solved. Sharing my experience
Updating formik values directly on OnChange method solved the issue
onChange={(event, value) => (formik.values.country = value!)}
Here is the full code
Formik settings
const formik = useFormik({
initialValues: {
id: user.id || "",
name: user.name || "",
country: user.country,
email: user.email || "",
submit: null,
},
validationSchema: Yup.object({
email: Yup.string()
.email("Must be a valid email")
.max(255)
.required("Email is required"),
name: Yup.string().max(255).required("Name is required"),
}),
onSubmit: async (values, helpers): Promise<void> => {
console.log("Updating user...");
try {
let userData: UserDetails = {
id: values.id,
email: values.email,
name: values.name,
country: values.country,
};
await userApi.registerUser(userData);
helpers.setStatus({ success: true });
helpers.setSubmitting(false);
toast.success("User updated!");
} catch (err) {
console.error(err);
toast.error("Something went wrong!");
helpers.setStatus({ success: false });
helpers.setErrors({ submit: err.message });
helpers.setSubmitting(false);
}
},
});
Autocomplete
<Autocomplete
getOptionLabel={(option): string => option.text}
options={countries}
value={formik.values.country}
defaultValue={formik.values.country}
onChange={(event, value) => (formik.values.country = value!)}
renderInput={(params): JSX.Element => (
<TextField
{...params}
fullWidth
label="Country"
name="country"
error={Boolean(
formik.touched.country && formik.errors.country
)}
helperText={formik.touched.country && formik.errors.country}
onBlur={formik.handleBlur}
/>
)}
/>