How to set value in MUI Select on onChange in Formik? - reactjs

I was facing some challenges while integrating Formik with the Select component of MUI, firstly, it didn't change the value on the onChange event and also was not showing any error messages , if I didn't select anything, so at last, I was able to find the solution and I have shared the answer below

Here, I am setting the value directly using formik.setFieldValue and passing the value to as we usually do, also to show error messages I have used FormHelperText from MUI
const formik = useFormik({
initialValues: {
user: '',
},
validationSchema: validationSchema,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
------------
<FormControl
fullWidth
size="small"
id="userType"
error={formik.touched.userType && Boolean(formik.errors.userType)}
>
<InputLabel id="user-type">User Type</InputLabel>
<Select
id="userType"
label="User Type"
name="userType"
value={formik.values.userType}
onBlur={formik.handleBlur}
onChange={(e) => formik.setFieldValue('userType', e.target.value as string)}
error={formik.touched.userType && Boolean(formik.errors.userType)}
>
<MenuItem value={'admin'}>Student</MenuItem>
<MenuItem value={'superAdmin'}>Recruiter</MenuItem>
</Select>
{formik.touched.password && (
<FormHelperText sx={{ color: 'error.main' }}>{formik.errors.userType}</FormHelperText>
)}
</FormControl>

Related

React Hook form validation does not work on a controlled component

I'm fairly new to react hook form. I have a custom component made on top of material ui's autocomplete. The problem is that react hook form is not validating the field whatsoever. Here's how react hook form is setup
type FormData = {
eventTypes: string[];
};
const FORM_SCHEMA = yup
.object()
.shape({
eventTypes: yup.array().of(yup.string()).required('Please select event type(s)'),
})
.required();
const formInitialValues: FormData = {
eventTypes: [],
};
const {
handleSubmit,
formState: { errors },
register,
watch,
control,
} = useForm<FormData>({ resolver: yupResolver(FORM_SCHEMA), defaultValues: formInitialValues });
return (
<Controller
name="eventTypes"
control={control}
render={({ field, fieldState: { error } }) => (
<MultipleSelection
{...field}
items={['Avg. Door Time', 'Door Cycles', 'Mileage', 'Trips']}
label="Event Type(s)"
placeholder="Select event type(s)"
required
showAllTag
freeSolo={false}
error={!!error}
helperText={!!error?.message ?? ''}
color={error ? 'error' : 'default'}
/>
)}
/>
);
The onChange of the component works fine and on submitting the form gets the values. But the validation on the field that it shouldn't be an empty array and is required is not working.
Here's how multipleSelection looks like
<Autocomplete
ref={autoCompleteContainerRef}
multiple
disabled={disabled}
value={value}
classes={{
root: classes.root,
focused: classes.inFocus,
popupIndicator: classes.icon,
popper: classes.popper,
paper: classes.paper,
}}
className={classes.multipleSelect}
options={items}
size={size}
closeIcon={
<Icon
component={CloseIcon}
fontSize="small"
classes={{ root: classes.icon }}
/>
}
autoComplete
onChange={(event, newValue) => {
handleChange(newValue);
}}
getLimitTagsText={(more) => (
<Chip
classes={{ root: clsx(classes.chip, classes.more) }}
label={
<Typography noWrap variant="caption">
{`+${more} more`}
</Typography>
}
/>
)}
renderTags={handleRenderTags}
renderInput={(params) => (
<TextField
{...params}
{...textFieldProps}
placeholder={isEmpty(value) ? placeholder : ''}
className={classes.textField}
onKeyDown={onKeyDown}
/>
)}
renderOption={(option, state) => (
<StyledListItem
label={optionLabel(option)}
selected={state.selected}
classes={{ label: classes.label }}
/>
)}
{...rest}
/>
Does anyone have an idea why the validation is not working. Any help would be appreciated.
As it turns out, the culprit was this piece of code
const FORM_SCHEMA = yup
.object()
.shape({
eventTypes: yup.array().of(yup.string()).required('Please select event type(s)'), // <---
})
.required();
I changed it to the following
const FORM_SCHEMA = yup
.object()
.shape({
eventTypes: yup
.array()
.of(yup.string())
.test('empty-check', 'Please select event type(s)', value => value?.length !== 0),
})
.required();
And the validation works now.
I'm still not sure why it was not working in the first place. If anybody has a reasoning to why yup.array().of(yup.string()).required this was failing. Please let me know.

How to apply Formik with Chakra in typescript?

I am trying to use following example on Chakra but using Typescript
<Formik
initialValues={{ name: "Sasuke" }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
>
{(props) => (
<Form>
<Field name="name" validate={validateName}>
{/* Trouble in this line below*/}
{({ field, form }) => (
<FormControl
isInvalid={form.errors.name && form.touched.name}
>
<FormLabel htmlFor="name">First name</FormLabel>
<Input {...field} id="name" placeholder="name" />
<FormErrorMessage>
{form.errors.name}
</FormErrorMessage>
</FormControl>
)}
</Field>
<Button
mt={4}
colorScheme="teal"
isLoading={props.isSubmitting}
type="submit"
>
Submit
</Button>
</Form>
)}
</Formik>
How do I define "field" and "form" in Typescript ? is there a prop for that ? or should I just define those things in an interface ?
I was encountering the same problem and gave a look at the source code.
You should be able to use FieldInputProps<T> where T is the value type you expect and FormikProps<T> where T is the type definition of the values you expect.
E.g.
{({ field, form }: { field: FieldInputProps<string>, form: FormikProps<{ name: string, surname: string }> }) => {
...
}}

How to use formik with Antd Design Form?

I am trying to implement formik validation with Ant design form, formik is able to capture the input/selected data but when I click onSubmit, it doesn't print anything in console.
export default function SearchForm() {
const formik = useFormik({
initialValues: {
city: "",
checkIn: "",
},
onSubmit: (values) => {
console.log(values)
},
});
//console.log(formik.values)
return (
<Form onSubmit={formik.handleSubmit}>
<FormLabel>Select City</FormLabel>
<Form.Item>
<Select
name="city"
value={formik.values.city}
placeholder="Please select a country"
onChange={(value) => formik.setFieldValue("city", value)}
>
<Select.Option value="mumbai">Mumbai</Select.Option>
<Select.Option value="bengaluru">Bengaluru</Select.Option>
<Select.Option value="delhi">Delhi</Select.Option>
<Select.Option value="hyderabad">Hyderabad</Select.Option>
<Select.Option value="chennai">Chennai</Select.Option>
</Select>
</Form.Item>
<FormLabel>Check-In-Date</FormLabel>
<Form.Item>
<DatePicker
name="checkIn"
value={formik.values.checkIn}
format={dateFormat}
onChange={(value) => formik.setFieldValue("checkIn", value)}
/>
</Form.Item>
<SubmitButton htmlType="submit">Book Now</SubmitButton>
</Form>
);
}
And by giving values, the placeholder text is not visible, and also in DatePicker, it is saving as Moment Object.
You can add Validation Schema in useFormik and import Yup, like this :
import * as Yup from 'yup';
const FormSchema = Yup.object().shape({
title: Yup.string().required('Please enter notification title')
});
<Formik
validationSchema={FormSchema}>
<Form>
<div className='grid-row'>
<div className='grid-1'>
<Field name='title' required={true} label='Title'component={Input} />
</div>
</Form>
</Formik>
finally, I got an answer!! When we use the Ant design form, it works a little different from the normal form, instead of using onSubmit we have to use onFinish and generally you pass value and function to capture event.target.value in normal form but in Ant design form we have to pass onChange={(value) => formik.setFieldValue("checkIn", value)} and it will simply capture the change.
<Form onFinish={formik.handleSubmit}>
<FormLabel>Select City</FormLabel>
<Form.Item>
<Select
name="city"
placeholder="Please select a country"
onChange={(value) => formik.setFieldValue("city", value)}
>
<Select.Option value="mumbai">Mumbai</Select.Option>
<Select.Option value="bengaluru">Bengaluru</Select.Option>
<Select.Option value="delhi">Delhi</Select.Option>
<Select.Option value="hyderabad">Hyderabad</Select.Option>
<Select.Option value="chennai">Chennai</Select.Option>
</Select>
</Form.Item>
<FormLabel>Check-In-Date</FormLabel>
<Form.Item>
<DatePicker
name="checkIn"
format={dateFormat}
onChange={(value) => formik.setFieldValue("checkIn", value)}
/>
</Form.Item>
<SubmitButton htmlType="submit">Book Now</SubmitButton>
</Form>

Form not Submitting in React using Formik and Yup

I'm using Autocomplete component in Material UI and I'm using Formik and Yup for validations.
My problem is that when I submit my values and console it. It doesn't appear which means it doesn't conform to its yup validation.
Pls check my codesandbox here
Click here
<Formik
validationSchema={citySchema}
initialValues={{ city_id: [{ id: null, name: "" }] }}
onSubmit={submit}
>
{({ handleChange, values, setFieldValue, touched, errors }) => (
<Form>
<Autocomplete
id="city_id"
name="city_id"
options={cities}
getOptionLabel={(option) => option.name}
style={{ width: 300 }}
onChange={(e, value) => {
setFieldValue("city_id", value);
}}
renderInput={(params) => (
<TextField
label="City"
variant="outlined"
helperText={touched.city_id && errors.city_id}
error={touched.city_id && Boolean(errors.city_id)}
{...params}
/>
)}
/>
<Button variant="contained" color="primary" type="submit">
Submit
</Button>
</Form>
)}
</Formik>
Your validationSchema requires a name but your code never supplies it.
Either make it optional or add a name input field.
An easy way to debug this is to print out the errors formik is supplying you:
<Form>
<pre>{JSON.stringify(errors, null, 2)}</pre>
<Autocomplete
...

A component is changing an uncontrolled Autocomplete to be controlled

Can you tell me that why I'm getting error "A component is changing an uncontrolled Autocomplete to be controlled.
Elements should not switch from uncontrolled to controlled (or vice versa).
Decide between using a controlled or uncontrolled Autocomplete element for the lifetime of the component."
component :
function AutoComplete(props) {
const defaultProps = {
options: props.options,
getOptionLabel: option => option.name,
};
const handleChange = (e, value) => {
props.onChange(value);
};
return (
<Autocomplete
{...defaultProps}
renderInput={params => (
<TextField {...params} label={props.label} margin="normal" />
)}
onChange={handleChange}
value={props.value}
/>
);
}
calling autocomplte:
<Controller
control={control}
name = 'country'
as = {
<AutoComplete
options={countryOptions}
onChange={selectCountryHandler}
label="Country"
value={selectedCountry || ''}
/>
} />
how can I solve this error?
You ensured that the value property never had been undefined, but you had to do same for inputValue.
the "value" state with the value/onChange props combination. This state represents the value selected by the user, for instance when pressing Enter.
the "input value" state with the inputValue/onInputChange props combination. This state represents the value displayed in the textbox.
⚠️ These two state are isolated, they should be controlled independently.
Component becomes uncontrolled when inputValue property is undefined, and vice versa.
If in the following example you delete an empty string from
React.useState('') you'll get the same error message because inputValue during first render is undefined.
import React from 'react'
import TextField from '#material-ui/core/TextField'
import Autocomplete from '#material-ui/lab/Autocomplete'
const options = ['Option 1', 'Option 2']
export default function AutocompleteLab() {
const [value, setValue] = React.useState(options[0])
const [inputValue, setInputValue] = React.useState('')
return (
<div>
<div>{`value: ${value !== null ? `'${value}'` : 'null'}`}</div>
<div>{`inputValue: '${inputValue}'`}</div>
<br />
<Autocomplete
value={value}
onChange={(_, newValue) => {
setValue(newValue)
}}
inputValue={inputValue}
onInputChange={(_, newInputValue) => {
setInputValue(newInputValue)
}}
options={options}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Name" variant="outlined" />}
/>
</div>
)
}
When no value is selected, you need to add || null to prevent the Autocomplete going into uncontrolled mode:
<Autocomplete {...props} value={props.value || null} />
If you pass value={undefined} to the Autocomplete component, it will start in "uncontrolled" mode, meaning it keeps its own internal state. Then if you later supply a value it raises the "A component is changing" error. But if you pass value={null}instead of value={undefined} that causes the Autocomplete to start in controlled mode. The Autocomplete will assume you will be providing the state, not keep its own, and the error goes away.
I solved this by removing the default value.
<Autocomplete
multiple
id="multiple-limit-tags"
options={(option) => option.label}
getOptionLabel={(option) => option}
// defaultValue={options || []}
renderInput={(params) => <TextField {...params} label="My Label" />}
/>
It wasn't obvious how to solve this, and the documentation doesn't help much either. I find it curious that a copy-pasted example from the documentation results in this error. I guess the example works because the choices are hard-coded.
Previous answer was absolutely correct, BUT I spend 20 minutes while I figure out that inputValue it should be value
So working example from me:
export default function AddModal(): ReactElement {
const [resource, setResource] = useState('one');
<Autocomplete
id="autocomplete"
options={['one', 'two']}
defaultValue={resource}
value={resource}
PopperComponent={StyledPopper}
onChange={(event, newInputValue) => setResource(newInputValue)}
renderInput={(params) => <TextField {...params} />}
/>
I had the same issue today, but I was able to solve it by providing a default value of null as well as providing a null value in case of it not existing. I'll leave the code that worked for me:
<Autocomplete
value={filters.tag || null}
defaultValue={null}
options={tags || []}
getOptionLabel={(option) => option}
renderInput={(params) => (
<TextField {...params} label="Search" variant='outlined' size='small' />
)}
fullWidth
onChange={(event, value) => {
if (value) {
setFilters({ ...filters, tag: value });
} else {
setFilters({ ...filters, tag: '' });
}
}}
/>
For me I fixed this issue by updated the onChange function and add || null rather then removing the default value since I still need it here is the code
<Box mt={2}>
<Controller
control={control}
name="thematic"
rules={{
required: 'Veuillez choisir une réponse',
}}
render={({ field: { onChange } }) => (
<Autocomplete
defaultValue={
questionData?.thematic ? questionData?.thematic : null
}
options={thematics}
getOptionLabel={(option) => option.name}
onChange={(event, values) => {
onChange(values || null)
}}
renderInput={(params) => (
<TextField
{...params}
label="Thématique"
placeholder="Thématique"
helperText={errors.thematic?.message}
error={!!errors.thematic}
/>
)}
/>
)}
/>
</Box>
For the autocomplete with options which is a simple array without object you can simply do it that way and u want get any issue
<Box mt={2}>
<Controller
control={control}
name="type"
rules={{
required: 'Veuillez choisir une réponse',
}}
render={({ field: { onChange, value } }) => (
<Autocomplete
freeSolo
options={['Champ', 'Sélection', 'Choix multiple']}
onChange={(event, values) => onChange(values)}
value={value}
renderInput={(params) => (
<TextField
{...params}
label="Type de la question"
variant="outlined"
onChange={onChange}
helperText={errors.type?.message}
error={!!errors.type}
/>
)}
/>
)}
/>
</Box>
the default value you can set it within useForm if you are using react-hook-form
const {
handleSubmit,
control,
watch,
register,
formState: { errors },
} = useForm({
defaultValues: {
...
type: questionData?.type ? mapperQuestionType[questionData?.type] : '',
},
})

Resources