MUI & Formik: Validation doesn't trigger for useField - reactjs

Codesandbox with minimal working example: https://codesandbox.io/s/mystifying-lake-m0oxc?file=/src/DemoForm.tsx
Essentially, I have a AutoComplete dropdown which is tied into a Formik form with the useField hook. This sets the value correctly on any change, but the validation doesn't seem to trigger when I expect it to.
Validation runs successfully and removes the error if:
Another field is changed
I click on the "background" after selecting a value in the dropdown
What I expected and wanted was that the validation should run immediately when a value is selected.
To reproduce:
Click the category dropdown
Select a value, which closes the dropdown
Confirm that the error is still indicated (on the field and in the printout)
Click elsewhere on the screen. This should trigger validation and clear the error.
Any suggestions?
Edit:
I've tested an equivalent implementation with react-select as the dropdown and had the same issue, so I don't think it's directly tied to MUI.

I just reproduced based on the working example what you provided and realized that you set helpers.setTouched manually.
Just don't overuse the setTouched and also you need to handle if you remove the selected item from the autocomplete.
<Autocomplete
disablePortal
id="category-selector"
options={options}
onBlur={() => helpers.setTouched(true, true)}
isOptionEqualToValue={(option, value) => option.id === value.id}
onOpen={() => helpers.setTouched(true, true)}
onChange={(_, option) => {
if (option) {
helpers.setValue(option.id);
} else {
helpers.setValue(0);
}
}}
renderInput={(params) => (
<TextField
{...params}
label="Category"
error={meta.touched && Boolean(meta.error)}
/>
)}
/>

Related

Using onSelect property of materialUI autocomplete form

I'm trying to create a search form with autocomplete suggestions that pulls data from API. I got everything to work in terms of displaying data and selection, but I would like to automatically take the user to the related page once they select one of the suggestions.
<Autocomplete
id="search-input"
freeSolo
disableClearable
options={playerList}
getOptionLabel={(option) => option.nickname}
style={{ width: 200}}
**onClick={console.log("you clicked")}
onSelect={(val)=> window.location.href = "/player-statistics/"+val.nickname+"-"+val.account_id+"-"+server}**
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
if (newInputValue.length >= 3) {fetchPlayers(newInputValue)}
}}
renderInput={(params) => <TextField {...params} label="Search Players" variant="outlined" margin="normal" />}
/>
I have tried with onChange and onSelect, but they are both reloading the page continuously whenever you press the search field or start typing.
The idea is to skip the need to click "search" button.
When you select a suggested name, onInputChange is triggered.
There you need to put the logic for changing the location.
Then the user does not have to click anything else.
If you do not want the page to change when the user only types a single character,
then you need to write logic for that inside onInputChange.
sth. like
// inside onInputChange
if(isASuggestedName(newInputValue)) {
//...change location
}
I figured it out after going through the API.
onChange call has 3 parameters - function(event: object, value: T | T[], reason: string) => void
I basically set it up so relocation is triggered only if reason is select-option and it works perfectly.
Thank you everyone

MUI's Autocomplete AS MULTIPLE input + react-hook-form + controlling default values not working (TypeError: Can't read property 'filter' of undefined)

I am trying to use Material-UI Autocomplete as multiple input with react-hook-form and control the defaultValues of the Autocomplete dynamically (render the component prefilled, when editing data, based on already saved data fetched from a database).
So the main question is:
What is the best way to control the default values in an Material-UI Autocomplete component and use it with react-hook-form?
What i did so far:
With using functions and hooks in react, I have wrapped an
Autocomplete component in React Hook Form's Controller to control
the state. I tried to implemented the solutions from the docs of MUI
and react-hook-form and the solutions of the below threads.
I created a minimal Sandbox here
What it does
When i set the defaultValue in Controller, it works to display the controlled default value, but throws me an error: TypeError: Cannot read property 'filter' of undefined
<Controller
as={
<Autocomplete
multiple
value={defaultValues}
onChange={(e, values) => setValue("food", values)}
...
renderInput={params => ( ... )}
/>
}
control={control}
name="food"
defaultValue={defaultValues} // <- this makes the error
/>
When i don't set the defaultValue in Controller, it works perfectly to being able to select multiple values like expected, but it doesn't show the default value.
What is so confusing is, that the Autocomplete has to be controlled with value/onChange and also the Controller has to control with defaultValue/setValue, and it seems they conflict in my case.
It works better when setting defaultValue={ [] } and using a
useEffect and controlling the default value only with
setValue("food", defaultOption);
So i created another Sandbox here
Thanks to Bill's answer i refactored the code to a renderProp
like proposed in the docs:
Yet another sandbox here
Now it works like a charm, but i had to set the onChange prop of
the Autocomplete like this:
onChange={(e, values) => setValue("food", values)}
instead of what the docs proposed to do: (using the passed onChange)
onChange={e => props.onChange(e.something)}
It works, but is this the right way to combine Autocomplete and
react-hook-form?
Compare the questions with these threads:
The main difference to the other threads i am trying to do, is to set the defaultValues of a multiple Autocomplete.
Proper way to use react-hook-form Controller with Material-UI Autocomplete
MUI Autocomplete's 'defaultValue' not working when used with Controller of react-hook-form
Why is initial value not set in Material UI Autocomplete using react-hook-form?
The advised solution in the docs of react-hook-form:
https://react-hook-form.com/api/#Controller
And the Code from Material UI docs:
https://material-ui.com/components/autocomplete/#multiple-values
I was able to get this working by doing the following:
<Controller
name='test'
control={control}
render={({onChange, ...props}) => (
<AutoComplete
{...props}
data-testid='test-select'
width={350}
label='Auto Complete'
onChange={onChange}
options={eventTypes}
getOptionLabel={(option) => option ? option.name : ''}
renderOption={(option) => option.name }
getOptionSelected={(option) => option.name}
renderInput={(params) => (
<TextField {...params} error={error} helperText={helperText} label={label} placeholder={label} />
)}
onChange={(e, data) => onChange(data)}
{...props}
/>
)}
/>
However, I've not found a way to validate this using react-hook-form
You could try custom validation (in case if you are using MUI's Autocomplete with multiple={true}):
<Controller
...
rules={{
validate: (data) => {
if(data.length === 0) return false;
}
}}
/>
Now it works like a charm, but i had to set the onChange prop of the
Autocomplete like this:
onChange={(e, values) => setValue("food", values)}
You can do onChange={(e, newValue) => props.onChange(newValue)}

Material-ui Autocomplete warning The value provided to Autocomplete is invalid

I am working with React and material-ui.. I just realize i have a warning with the Autocomplete component when i try to submit the form, so i tried to do something really basic just like in the documentation:
let Form = props => {
return(
<form noValidate onSubmit={handleSubmit} >
<Autocomplete
id="combo-box-demo"
options={[{id:1,name:"test"},{id:2, name:"test2"}]}
getOptionLabel={(option) => option.name}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Combo box" variant="outlined" />}
/>
and when i try to submit the form i get the following error:
Material-UI: The value provided to Autocomplete is invalid.
None of the options match with {"id":1,"name":"test"}.
You can use the getOptionSelected prop to customize the equality test.
I also realize that if i set the options in the state of the component there is no warning (just when they are set like a constant). So i wonder if some of you have any idea of this behavior? thank you so much in advance.
Basically the reason why you get the warning is a default implementation of getOptionSelected in version 4.x.x:
getOptionSelected = (option, value) => option === value
In your case, selecting a value the following comparison happens:
// option === value:
{id:1, name:"test"} === {id:1, name:"test"} // false
Obviously, it can be true in some circumstances. In this particular case, it's false because of objects pointing to the different instances.
Solution! You have to overwrite getOptionSelected implementation:
<Autocomplete
getOptionSelected={(option, value) => option.id === value.id}
...otherProps
/>
[Update]
Note, in version 5.x.x the prop was renamed:
- getOptionSelected={(option, value) => option.id === value.id}
+ isOptionEqualToValue={(option, value) => option.id === value.id}
version 5.0
isOptionEqualToValue={(option, value) => option.value === value.value}
Also when you want to build a searcher where value you write is not necesary the same as the options you can set freeSolo to true and the warning will disapear
This Worked,
getOptionSelected={(option, value) => option.value === value.value}
https://github.com/mui-org/material-ui/issues/18514#issuecomment-606854194
Following up on elVengadors Answer:
When you want to build a searcher where the value you type in the box (the inputValue) is not necessarily one of the provided options you can set freeSolo to true.
This will stop the warning message from being displayed.
The need for this might arise if you are creating a component that allows for asynchronous querying of an API. This would cause the value of options to change based on the response from the API, but options that have already been selected before changing the inputValue to query the API may not be included in this new list of options.
In the Autocomplete Component Documentation, freeSolo is described as:
If true, the Autocomplete is free solo, meaning that the user input is not bound to provided options.
Bonus
Setting freeSolo to true will remove the pop-up button (the drop-down arrow on the right side of the Autocomplete component). To retain this button, you should also add forcePopupIcon={true}.
I had same problem after adding getOptionSelected error gone.
Error:
<Autocomplete
fullWidth={true}
label={'Location'}
margin={'noraml'}
multiple={false}
name={'location'}
value={formValues.location === '' ? {label: ''} : {label: formValues.location}}
options={location}
ref={locationRef}
onChange={useCallback((e, v) => handleInputChange(e, v))}
/>
Solution: getOptionSelected property added
<Autocomplete
fullWidth={true}
label={'Location'}
margin={'noraml'}
multiple={false}
name={'location'}
getOptionSelected={useCallback((option, value) => option.value === value.value)} // added
value={formValues.location === '' ? {label: ''} : {label: formValues.location}}
options={location}
ref={locationRef}
onChange={useCallback((e, v) => handleInputChange(e, v))}
/>
I think you should not use <form> to wrap AutoComplete component. You should set value for AutoComplete and use a function to handle on click button to submit.
Try this:
let Form = props => {
const [value, setValue] = useState({})
const handleOnSubmit = (value) => {
setValue(value)
...
}
return(
<div>
<Autocomplete
id="combo-box-demo"
value={value}
options={[{id:1,name:"test"},{id:2, name:"test2"}]}
getOptionLabel={(option) => option.name}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Combo box" variant="outlined" />}
/>
<Button onClick={() => handleOnSubmit(value)}>Submit</Button>
</div>
)
}

Material-ui autocomplete clear value

I have one problem in my react code.
I use Material-ui and redux-form. I have select input like and after change this select i should reset value in . I use action 'change' from react-form and set value for textfield. But label in still remains. Can i clear or reset value in ?
<Autocomplete
options={list}
getOptionLabel={option => option.name}
onInputChange={onChange}
onChange={onChangeAutoComplete}
noOptionsText='Нет доступных вариантов'
loadingText='Загрузка...'
openText='Открыть'
renderInput={params => (
<Field
{...params}
label={label}
name={fieldName}
variant="outlined"
fullWidth
component={renderTextField}
className={classes.textField}
margin="normal"
/>
)}
/>
Using hooks on the value prop breaks the functionality of the autocomplete component ( at least for me ). Using class, and setting the local state is the same.
Luckily it is a react component, so it have a "key" prop. When the key prop changes, the component is re-rendered with the default values ( which is an empty array since nothing is selected). I used hooks in the parent component and passed the values to the key prop, whenever reset is needed.
<Autocomplete
key={somethingMeaningful} // Bool, or whatever just change it to re-render the component
//...other props
/>
Hope this helps!
Material UI Autocomplete onInputChange callback provides reason argument. If input has been changed by input, reason will be input and if you selected option then reason will be reset.
onInputChange={(event, newInputValue, reason) => {
if (reason === 'reset') {
setValue('')
return
} else {
setValue(newInputValue)
}
}}
setValue is useState and you can pass value state to autocomplete value property.
use value in your <Autocomplete /> like this:
<Autocomplete
value={this.state.value} //insert your state key here
//...other props
/>
Then clear state of that key, to clear the autocomplete field value
I am going to post a very dirty way of clearing the value of Autocomplete. Try it ONLY when nothing else works;
import React, { useRef } from 'react';
...
const autoC = useRef(null);
...
<Autocomplete
...
ref={autoC}
/>
and then when you want to clear the value;
const ele = autoC.current.getElementsByClassName('MuiAutocomplete-clearIndicator')[0];
if (ele) ele.click();
This is what worked for me.
const [name, setName] = useState('');
<Autocomplete
inputValue={name}
onChange={(e,v)=>setName(v?.name||v)}
...
/>
<Button onClick={()=>setName('')}>
Clear
</Button>
You can use something like the following to clear the autocomplete field when an item is selected.
<Autocomplete
value={null}
blurOnSelect={true} />
Note that you may also need to set clearOnBlur={true} if you're using the freeSolo option.
Source https://mui.com/api/autocomplete/#props
I achieved this by updating the inputValue prop where multiple prop is false. If you are using multiple prop, then there is a propblem (bug). Selected values does not get erased.
When I encountered this, it was when options for the autocomplete changed, and wanted to clear the input value. It wouldn't clear with just the options changing. What worked for me is adding a key value onto the autocomplete which depended on the change which necessitated clearing.
To solve this, I created a hook that watches the value state of the autocomplete and set the value of the input if the checkClear returns true;
function useAutocompleteInputClear(watch, checkClear) {
const elmRef = useRef(null);
useMemo(() => {
if (!elmRef || !elmRef.current) return;
if (!checkClear || typeof checkClear !== "function") return;
const button = elmRef.current.querySelector("button")
if (checkClear(watch) && button) {
button.click();
}
}, [watch])
return elmRef;
}
Its first argument is the value that should be watched and its second argument is a function that returns a boolean. if it is true the clearing will happen.
Also, the hook returns a ref that needs to pass as ref prop to Autocomplete.
const elmRef = useAutocompleteInputClear(value, v => !v || !v.id)
<Autocomplete ref={elmRef}
value={value}
...
using onChange property we can clear the value by clicking the clear icon in the following way
<Autocomplete
fullWidth={true}
label={'Source'}
margin={'noraml'}
multiple={false}
name={'Source'}
getOptionSelected={useCallback((option, value) => option.value === value.value)}
ref={SourceRef}
value={formValues.Source === '' ? {label: ''} : {label: formValues.Source}}
options={SourceStatus}
onChange={useCallback((e, v) => {
if (typeof v === 'object' && v !== null) {
handleInputChange(e, v) // help to set the value
} else {
handleInputChange(e, {label: ''}) // help to reset the value
}
})}
/>
In my case for multiselect freeSolo onChange props 3rd argument reason solved my all issues.
AutocompleteChangeReason can be:
blur
clear
createOption
removeOption
selectOption
and 2nd arg of this props gives u already updated list of (multiselect) value/s.
onChange={(_event, newOptions, reason) => {
setOptions(
reason === 'clear' ? [] : [...newOptions.map((o) => Number(o))],
);
}}
If you need only the selected value, set the value to an empty object and render the option to your needs.
<Autocomplete
value={{}}
onChange={handleSelectionChanged}
options={options ?? []}
getOptionLabel={x => (!x ? '' : x?.name ?? '')}
renderInput={params => <TextField {...params} label="" />}
/>
If you are using objects, you can use the following code to clear the field.
Ensure you add isOptionEqualToValue:
<Autocomplete
style={{ width: 250 }}
multiple
id="checkboxes-tags-demo"
options={list}
isOptionEqualToValue={(option, newValue) => {
return option.id === newValue.id;
}}
value={selected}
onChange={(e, val) => handleSelected(e, val)}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<TextField
{...params}
label="Add to Multiple"
placeholder="Favorites" />
)} />
Just set an empty array in your state through functions, and it'll be cleared.
Try this method:
use onChange method and pass third parameter reason and compare to clear text if reason is clear then executed this function.
<Autocomplete
onChange={(event, newValue, reason) => {
if (reason === 'clear') {
console.log("Put your clear logic here: this condition executed when clear button clicked")
setValue({ title: '', year: '' }) //for reset the value
return
}
}}
/>
One easy way to do this is to pass these props to autocomplete like this:
onChange={handleSkillChange}
inputValue=''
clearOnBlur={true}
onChange is an event handler, which stores the value in the state.
inputValue='' helps to ensure that the text field inside autocomplete will always be empty
clearOnBlur={true} helps to clear the value of the autocomplete component when it loses focus.

How to customize clear behaviour of Material-UI Autocomplete?

I have a controlled Autocomplete component bound to a state prop from redux, that's all working. The onChange event is fired when the user clicks the "clear" icon, however the input is focused and the dropdown opens when this occurs.
How can I prevent the input focus / dropdown open only when the selected option is cleared? My preferred behaviour would be for it to return to having "null" selected and showing the label un-shrunk.
The code looks roughly like this. value is sourced from mapStateToProps, onChange comes from mapDispatchToProps.
const LetterSelect: FC<Props> = ({ value, onChange }: Props) => {
return (
<Autocomplete
options={["A", "B", "C", "D"]}
value={value ?? null}
onChange={(e,v) => onChange(v)}
renderInput={params => (
<TextField {...params} label="Letter" variant="outlined" fullWidth />
)}
/>
)
}
Sorry I didn't get back to this sooner: there actually wasn't a direct fix for this.
Instead, I opened an issue on the Github and it was agreed to change the default behaviour - refer to this PR.
Clicking the Clear icon no longer opens the Autocomplete as of v4.8.1

Resources